• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mia-platform / crud-service / 7555241289

17 Jan 2024 11:48AM UTC coverage: 96.995% (-0.01%) from 97.009%
7555241289

push

github

danibix95
build(deps): bump the miaplatform group with 1 update

Bumps the miaplatform group with 1 update: [@mia-platform/lc39](https://github.com/mia-platform/lc39).


Updates `@mia-platform/lc39` from 7.0.2 to 7.0.3
- [Release notes](https://github.com/mia-platform/lc39/releases)
- [Changelog](https://github.com/mia-platform/lc39/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mia-platform/lc39/compare/v7.0.2...v7.0.3)

---
updated-dependencies:
- dependency-name: "@mia-platform/lc39"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: miaplatform
...

Signed-off-by: dependabot[bot] <support@github.com>

1709 of 1854 branches covered (0.0%)

Branch coverage included in aggregate %.

8588 of 8762 relevant lines covered (98.01%)

7314.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.35
/lib/loadModels.js
1
/*
88✔
2
 * Copyright 2023 Mia s.r.l.
88✔
3
 *
88✔
4
 * Licensed under the Apache License, Version 2.0 (the "License");
88✔
5
 * you may not use this file except in compliance with the License.
88✔
6
 * You may obtain a copy of the License at
88✔
7
 *
88✔
8
 *     http://www.apache.org/licenses/LICENSE-2.0
88✔
9
 *
88✔
10
 * Unless required by applicable law or agreed to in writing, software
88✔
11
 * distributed under the License is distributed on an "AS IS" BASIS,
88✔
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88✔
13
 * See the License for the specific language governing permissions and
88✔
14
 * limitations under the License.
88✔
15
 */
88✔
16

88✔
17
'use strict'
88✔
18

88✔
19
const Ajv = require('ajv')
88✔
20
const ajvFormats = require('ajv-formats')
88✔
21
const ajvKeywords = require('ajv-keywords')
88✔
22
const lomit = require('lodash.omit')
88✔
23
const plimit = require('p-limit')
88✔
24

88✔
25
const mergeViewsInCollections = require('./mergeViewsInCollections')
88✔
26
const { compatibilityModelJsonSchema, modelJsonSchema } = require('./model.jsonschema')
88✔
27
const createIndexes = require('./createIndexes')
88✔
28
const { OBJECTID, aggregationConversion } = require('./consts')
88✔
29
const JSONSchemaGenerator = require('./JSONSchemaGenerator')
88✔
30
const AdditionalCaster = require('./AdditionalCaster')
88✔
31
const QueryParser = require('./QueryParser')
88✔
32
const CrudService = require('./CrudService')
88✔
33
const generatePathFieldsForRawSchema = require('./generatePathFieldsForRawSchema')
88✔
34
const { getIdType } = require('./mongo/mongo-plugin')
88✔
35
const { getDatabaseNameByType } = require('./pkFactories')
88✔
36

88✔
37
const ajv = new Ajv({ useDefaults: true })
88✔
38
ajvFormats(ajv)
88✔
39
ajvKeywords(ajv)
88✔
40

88✔
41
const compatibilityValidate = ajv.compile(compatibilityModelJsonSchema)
88✔
42
const validate = ajv.compile(modelJsonSchema)
88✔
43

88✔
44
const PREFIX_OF_INDEX_NAMES_TO_PRESERVE = 'preserve_'
88✔
45
const VIEW_TYPE = 'view'
88✔
46

88✔
47
const limiter = plimit(5)
88✔
48

88✔
49
async function loadModels(fastify) {
210✔
50
  const { collections, views = [] } = fastify
210✔
51
  const mergedCollections = mergeViewsInCollections(collections, views)
210✔
52

210✔
53
  fastify.log.trace({ collectionNames: mergedCollections.map(coll => coll.name) }, 'Registering CRUDs and Views')
210✔
54

210✔
55
  // A side-effect from the collectionModelMapper updates the following data models.
210✔
56
  const models = {}
210✔
57
  const existingStringCollection = []
210✔
58
  const existingObjectIdCollection = []
210✔
59

210✔
60
  const promises = mergedCollections.map(
210✔
61
    (collectionDefinition) => limiter(() =>
210✔
62
      collectionModelMapper(
4,104✔
63
        fastify,
4,104✔
64
        mergedCollections,
4,104✔
65
        { models, existingStringCollection, existingObjectIdCollection }
4,104✔
66
      )(collectionDefinition)
4,104✔
67
    )
4,104✔
68
  )
210✔
69
  await Promise.all(promises)
210✔
70
  fastify.decorate('models', models)
210✔
71
}
210✔
72

88✔
73
function collectionModelMapper(
4,104✔
74
  fastify,
4,104✔
75
  mergedCollections,
4,104✔
76
  // The previous implementation of the mapper was with a closure function that applied a
4,104✔
77
  // side-effect on these values. During the refactor to apply p-limit I've decided not to
4,104✔
78
  // change the implementation, therefore I've wrapped the params to isolate them.
4,104✔
79
  { models, existingStringCollection, existingObjectIdCollection }
4,104✔
80
) {
4,104✔
81
  // eslint-disable-next-line max-statements
4,104✔
82
  return async(collectionDefinition) => {
4,104✔
83
    // avoid validating the collection definition twice, since it would only
4,104✔
84
    // match one of the two, depending on the existence of schema property
4,104✔
85
    if (!collectionDefinition.schema) {
4,104✔
86
      if (!compatibilityValidate(collectionDefinition)) {
4,080!
87
        fastify.log.error({ collection: collectionDefinition.name }, compatibilityValidate.errors)
×
88
        throw new Error(`invalid collection definition: ${JSON.stringify(compatibilityValidate.errors)}`)
×
89
      }
×
90
    } else if (!validate(collectionDefinition)) {
4,104!
91
      fastify.log.error(validate.errors)
×
92
      throw new Error(`invalid collection definition: ${JSON.stringify(validate.errors)}`)
×
93
    }
×
94

4,104✔
95
    const {
4,104✔
96
      source: viewSourceCollectionName,
4,104✔
97
      name: collectionName,
4,104✔
98
      endpointBasePath: collectionEndpoint,
4,104✔
99
      type,
4,104✔
100
      indexes = [],
4,104✔
101
      enableLookup,
4,104✔
102
      source,
4,104✔
103
      pipeline,
4,104✔
104
    } = collectionDefinition ?? {}
4,104!
105
    const isView = type === VIEW_TYPE
4,104✔
106
    const viewLookupsEnabled = isView && enableLookup
4,104✔
107

4,104✔
108
    fastify.log.trace({ collectionEndpoint, collectionName }, 'Registering CRUD')
4,104✔
109

4,104✔
110
    const collectionIdType = getIdType(collectionDefinition)
4,104✔
111
    const collection = await fastify
4,104✔
112
      .mongo[getDatabaseNameByType(collectionIdType)]
4,104✔
113
      .db
4,104✔
114
      .collection(collectionName)
4,104✔
115
    const modelDependencies = buildModelDependencies(fastify, collectionDefinition, collection)
4,104✔
116

4,104✔
117
    let viewDependencies = {}
4,104✔
118
    if (viewLookupsEnabled) {
4,104✔
119
      const sourceCollection = mergedCollections.find(mod => mod.name === viewSourceCollectionName)
12✔
120
      const sourceCollectionDependencies = buildModelDependencies(fastify, sourceCollection)
12✔
121

12✔
122
      const viewIdType = getIdType(sourceCollection)
12✔
123
      const sourceCollectionMongo = await fastify
12✔
124
        .mongo[getDatabaseNameByType(viewIdType)]
12✔
125
        .db
12✔
126
        .collection(viewSourceCollectionName)
12✔
127
      viewDependencies = buildModelDependencies(fastify, collectionDefinition, sourceCollectionMongo)
12✔
128
      viewDependencies.queryParser = sourceCollectionDependencies.queryParser
12✔
129
      viewDependencies.allFieldNames = sourceCollectionDependencies.allFieldNames
12✔
130
      viewDependencies.lookupsModels = createLookupModel(fastify, collectionDefinition, mergedCollections)
12✔
131
    }
12✔
132

4,104✔
133
    models[getCollectionNameFromEndpoint(collectionEndpoint)] = {
4,104✔
134
      definition: collectionDefinition,
4,104✔
135
      ...modelDependencies,
4,104✔
136
      viewDependencies,
4,104✔
137
      isView,
4,104✔
138
      viewLookupsEnabled,
4,104✔
139
    }
4,104✔
140

4,104✔
141
    if (isView) {
4,104✔
142
      const existingCollections = collectionIdType === OBJECTID ? existingObjectIdCollection : existingStringCollection
606✔
143
      if (existingCollections.length === 0) {
606✔
144
        existingCollections.push(
605✔
145
          ...(
605✔
146
            await fastify
605✔
147
              .mongo[getDatabaseNameByType(collectionIdType)]
605✔
148
              .db
605✔
149
              .listCollections({}, { nameOnly: true })
605✔
150
              .toArray()
605✔
151
          )
605✔
152
            .filter(({ type: collectionType }) => collectionType === VIEW_TYPE)
605✔
153
            .map(({ name }) => name)
605✔
154
        )
605✔
155
      }
605✔
156

606✔
157
      if (existingCollections.includes(collectionName)) {
606!
158
        await fastify
×
159
          .mongo[getDatabaseNameByType(collectionIdType)]
×
160
          .db
×
161
          .collection(collectionName)
×
162
          .drop()
×
163
      }
×
164

606✔
165
      return fastify
606✔
166
        .mongo[getDatabaseNameByType(collectionIdType)]
606✔
167
        .db
606✔
168
        .createCollection(
606✔
169
          collectionName,
606✔
170
          {
606✔
171
            viewOn: source,
606✔
172
            pipeline,
606✔
173
          }
606✔
174
        )
606✔
175
    }
606✔
176

3,498✔
177
    return createIndexes(collection, indexes, PREFIX_OF_INDEX_NAMES_TO_PRESERVE)
3,498✔
178
  }
4,104✔
179
}
4,104✔
180

88✔
181
function getCollectionNameFromEndpoint(endpointBasePath) {
4,104✔
182
  return endpointBasePath.replace('/', '').replace(/\//g, '-')
4,104✔
183
}
4,104✔
184

88✔
185
function createLookupModel(fastify, viewDefinition, mergedCollections) {
12✔
186
  const lookupModels = []
12✔
187
  const viewLookups = viewDefinition.pipeline
12✔
188
    .filter(pipeline => '$lookup' in pipeline)
12✔
189
    .map(lookup => Object.values(lookup).shift())
12✔
190

12✔
191
  for (const lookup of viewLookups) {
12✔
192
    const { from, pipeline } = lookup
12✔
193
    const lookupCollection = mergedCollections.find(({ name }) => name === from)
12✔
194
    const lookupIdType = getIdType(lookupCollection)
12✔
195
    const lookupCollectionMongo = fastify.mongo[getDatabaseNameByType(lookupIdType)].db.collection(from)
12✔
196

12✔
197
    const lookupProjection = pipeline?.find(({ $project }) => $project)?.$project ?? {}
12!
198
    const parsedLookupProjection = []
12✔
199
    const lookupCollectionDefinition = {
12✔
200
      ...lomit(viewDefinition, ['fields']),
12✔
201
      schema: {
12✔
202
        type: 'object',
12✔
203
        properties: {},
12✔
204
        required: [],
12✔
205
      },
12✔
206
    }
12✔
207

12✔
208
    Object.entries(lookupProjection)
12✔
209
      .forEach(([fieldName, schema]) => {
12✔
210
        parsedLookupProjection.push({ [fieldName]: schema })
36✔
211
        const conversion = Object.keys(schema).shift()
36✔
212
        if (schema !== 0) {
36✔
213
          if (!aggregationConversion[conversion]) {
24!
214
            throw new Error(`Invalid view lookup definition: no explicit type found in ${JSON.stringify({ [fieldName]: schema })}`)
×
215
          }
×
216
          lookupCollectionDefinition.schema.properties[fieldName] = {
24✔
217
            type: aggregationConversion[conversion],
24✔
218
          }
24✔
219
        }
24✔
220
      })
12✔
221

12✔
222
    const lookupModel = {
12✔
223
      ...buildModelDependencies(fastify, lookupCollectionDefinition, lookupCollectionMongo),
12✔
224
      definition: lookupCollectionDefinition,
12✔
225
      lookup,
12✔
226
      parsedLookupProjection,
12✔
227
    }
12✔
228
    lookupModels.push(lookupModel)
12✔
229
  }
12✔
230
  return lookupModels
12✔
231
}
12✔
232

88✔
233
function buildModelDependencies(fastify, collectionDefinition, collection) {
4,140✔
234
  const {
4,140✔
235
    defaultState,
4,140✔
236
  } = collectionDefinition
4,140✔
237

4,140✔
238
  const allFieldNames = collectionDefinition.fields
4,140✔
239
    ? collectionDefinition.fields.map(field => field.name)
4,140✔
240
    : Object.keys(collectionDefinition.schema.properties)
4,140✔
241
  const pathsForRawSchema = generatePathFieldsForRawSchema(fastify.log, collectionDefinition)
4,140✔
242

4,140✔
243
  // TODO: make this configurable
4,140✔
244
  const crudService = new CrudService(
4,140✔
245
    collection,
4,140✔
246
    defaultState,
4,140✔
247
    { allowDiskUse: fastify.config.ALLOW_DISK_USE_IN_QUERIES },
4,140✔
248
  )
4,140✔
249
  const queryParser = new QueryParser(collectionDefinition, pathsForRawSchema)
4,140✔
250
  const additionalCaster = new AdditionalCaster(collectionDefinition)
4,140✔
251
  const jsonSchemaGenerator = new JSONSchemaGenerator(
4,140✔
252
    collectionDefinition,
4,140✔
253
    {},
4,140✔
254
    fastify.config.CRUD_LIMIT_CONSTRAINT_ENABLED,
4,140✔
255
    fastify.config.CRUD_MAX_LIMIT,
4,140✔
256
    fastify.config.ENABLE_STRICT_OUTPUT_VALIDATION
4,140✔
257
  )
4,140✔
258
  const jsonSchemaGeneratorWithNested = new JSONSchemaGenerator(
4,140✔
259
    collectionDefinition,
4,140✔
260
    pathsForRawSchema,
4,140✔
261
    fastify.config.CRUD_LIMIT_CONSTRAINT_ENABLED,
4,140✔
262
    fastify.config.CRUD_MAX_LIMIT,
4,140✔
263
    fastify.config.ENABLE_STRICT_OUTPUT_VALIDATION
4,140✔
264
  )
4,140✔
265

4,140✔
266
  return {
4,140✔
267
    crudService,
4,140✔
268
    queryParser,
4,140✔
269
    castResultsAsStream: () => additionalCaster.castResultsAsStream(),
4,140✔
270
    castItem: (item) => additionalCaster.castItem(item),
4,140✔
271
    allFieldNames,
4,140✔
272
    jsonSchemaGenerator,
4,140✔
273
    jsonSchemaGeneratorWithNested,
4,140✔
274
  }
4,140✔
275
}
4,140✔
276

88✔
277
module.exports = loadModels
88✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc