jskos-server 2.4.0

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 (112) hide show
  1. package/.dockerignore +20 -0
  2. package/.editorconfig +9 -0
  3. package/.github/workflows/docker.yml +59 -0
  4. package/.github/workflows/gh-pages.yml +23 -0
  5. package/.github/workflows/gh-release.yml +19 -0
  6. package/.github/workflows/test.yml +39 -0
  7. package/.husky/pre-commit +1 -0
  8. package/CHANGELOG.md +18 -0
  9. package/LICENSE +21 -0
  10. package/README.md +2710 -0
  11. package/bin/extra.js +81 -0
  12. package/bin/import.js +438 -0
  13. package/bin/mongodb.js +21 -0
  14. package/bin/reset.js +257 -0
  15. package/bin/upgrade.js +34 -0
  16. package/config/config.default.json +88 -0
  17. package/config/config.schema.json +877 -0
  18. package/config/config.test.json +107 -0
  19. package/config/index.js +77 -0
  20. package/config/setup.js +212 -0
  21. package/depdendencies.png +0 -0
  22. package/docker/.env +1 -0
  23. package/docker/Dockerfile +20 -0
  24. package/docker/README.md +175 -0
  25. package/docker/docker-compose.yml +29 -0
  26. package/docker/docker-entrypoint.sh +8 -0
  27. package/docker/mongo-initdb.d/mongo_setup.sh +22 -0
  28. package/ecosystem.example.json +7 -0
  29. package/errors/index.js +94 -0
  30. package/eslint.config.js +17 -0
  31. package/index.js +10 -0
  32. package/models/annotations.js +13 -0
  33. package/models/concepts.js +12 -0
  34. package/models/concordances.js +12 -0
  35. package/models/index.js +33 -0
  36. package/models/mappings.js +20 -0
  37. package/models/meta.js +21 -0
  38. package/models/registries.js +12 -0
  39. package/models/schemes.js +12 -0
  40. package/package.json +91 -0
  41. package/routes/annotations.js +83 -0
  42. package/routes/common.js +86 -0
  43. package/routes/concepts.js +64 -0
  44. package/routes/concordances.js +86 -0
  45. package/routes/data.js +19 -0
  46. package/routes/mappings.js +108 -0
  47. package/routes/registries.js +24 -0
  48. package/routes/schemes.js +72 -0
  49. package/routes/validate.js +37 -0
  50. package/server.js +190 -0
  51. package/services/abstract.js +328 -0
  52. package/services/annotations.js +237 -0
  53. package/services/concepts.js +459 -0
  54. package/services/concordances.js +264 -0
  55. package/services/data.js +30 -0
  56. package/services/index.js +34 -0
  57. package/services/mappings.js +978 -0
  58. package/services/registries.js +319 -0
  59. package/services/schemes.js +318 -0
  60. package/services/validate.js +39 -0
  61. package/status.schema.json +145 -0
  62. package/test/abstract-service.js +36 -0
  63. package/test/annotations/annotation.json +13 -0
  64. package/test/api.js +2481 -0
  65. package/test/chai.js +14 -0
  66. package/test/changes.js +179 -0
  67. package/test/concepts/conceptNoFileEnding +4 -0
  68. package/test/concepts/concepts-ddc-6-60-61-62.json +123 -0
  69. package/test/concordances/concordances.ndjson +2 -0
  70. package/test/config.js +26 -0
  71. package/test/configs/complex-config.json +90 -0
  72. package/test/configs/empty-object.json +1 -0
  73. package/test/configs/fail-array.json +1 -0
  74. package/test/configs/fail-empty.json +0 -0
  75. package/test/configs/fail-mapping-only-props1.json +5 -0
  76. package/test/configs/fail-mapping-only-props2.json +5 -0
  77. package/test/configs/fail-mapping-only-props3.json +5 -0
  78. package/test/configs/fail-mapping-only-props4.json +5 -0
  79. package/test/configs/fail-nonexisting-prop.json +3 -0
  80. package/test/configs/fail-port-string.json +3 -0
  81. package/test/configs/fail-registry-types.json +16 -0
  82. package/test/configs/registry-types.json +16 -0
  83. package/test/data-write.js +784 -0
  84. package/test/eslint.js +22 -0
  85. package/test/import-reset.js +287 -0
  86. package/test/infer-mappings.js +340 -0
  87. package/test/ipcheck.js +287 -0
  88. package/test/mappings/README.md +1 -0
  89. package/test/mappings/ddc-gnd-1.mapping.json +33 -0
  90. package/test/mappings/ddc-gnd-2.mapping.json +67 -0
  91. package/test/mappings/mapping-ddc-gnd-noScheme.json +145 -0
  92. package/test/mappings/mapping-ddc-gnd.json +175 -0
  93. package/test/mappings/mappings-ddc.json +214 -0
  94. package/test/registries/registries.ndjson +2 -0
  95. package/test/services.js +557 -0
  96. package/test/terminologies/terminologies.json +94 -0
  97. package/test/test-utils.js +182 -0
  98. package/test/utils.js +425 -0
  99. package/test/validate.js +226 -0
  100. package/utils/adjust.js +206 -0
  101. package/utils/auth.js +154 -0
  102. package/utils/changes.js +88 -0
  103. package/utils/db.js +106 -0
  104. package/utils/ipcheck.js +76 -0
  105. package/utils/middleware.js +636 -0
  106. package/utils/searchHelper.js +153 -0
  107. package/utils/status.js +77 -0
  108. package/utils/users.js +7 -0
  109. package/utils/utils.js +114 -0
  110. package/utils/uuid.js +6 -0
  111. package/utils/version.js +324 -0
  112. package/views/base.ejs +172 -0
package/bin/extra.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ import yesno from "yesno"
4
+ import * as db from "../utils/db.js"
5
+ import { schemeService } from "../services/schemes.js"
6
+ import jskos from "jskos-tools"
7
+ import { Mapping } from "../models/mappings.js"
8
+
9
+ /**
10
+ * Map of async scripts.
11
+ *
12
+ * Note that a connection to the database will be established before the script is called.
13
+ */
14
+ const scripts = {
15
+ async supplementNotationsInMappings() {
16
+ const mappings = await Mapping.find({
17
+ $or: [
18
+ { "from.memberSet.notation": { $exists: false }, "from.memberSet.uri": { $exists: true } },
19
+ { "to.memberSet.notation": { $exists: false }, "to.memberSet.uri": { $exists: true } },
20
+ ],
21
+ })
22
+
23
+ console.log(`Found ${mappings.length} mappings with missing notations...`)
24
+
25
+ if (mappings.length && !(await yesno({
26
+ question: `Update ${mappings.length} mappings with notations (if possible)?`,
27
+ defaultValue: false,
28
+ }))) {
29
+ console.log("Aborting...")
30
+ await db.disconnect()
31
+ return
32
+ }
33
+
34
+ let changedMappings = 0
35
+ for (const mapping of mappings) {
36
+ const changedPaths = []
37
+ for (const side of ["from", "to"]) {
38
+ let scheme = mapping[`${side}Scheme`]
39
+ let concepts = mapping[side].memberSet
40
+ scheme = await schemeService.getScheme(scheme.uri)
41
+ scheme = scheme && new jskos.ConceptScheme(scheme)
42
+ if (!scheme) {
43
+ continue
44
+ }
45
+ concepts.forEach((concept, index) => {
46
+ if (concept.notation && concept.notation.length) {
47
+ return
48
+ }
49
+ const notation = scheme.notationFromUri(concept.uri)
50
+ if (!notation) {
51
+ return
52
+ }
53
+ concept.notation = [notation]
54
+ changedPaths.push(`${side}.memberSet.${index}.notation`)
55
+ })
56
+ }
57
+ if (changedPaths.length) {
58
+ changedPaths.forEach(path => mapping.markModified(path))
59
+ await mapping.save()
60
+ changedMappings += 1
61
+ }
62
+ }
63
+
64
+ console.log(`- Supplemented ${changedMappings} mappings with notations.`)
65
+ console.log(`- ${mappings.length - changedMappings} mappings could not be adjusted.`)
66
+ },
67
+ }
68
+
69
+ const scriptName = process.argv[2]
70
+
71
+ if (!scripts[scriptName]) {
72
+ console.error(`No supplemental script with name ${scriptName} could be found. The following scripts are available:`)
73
+ Object.keys(scripts).forEach(name => console.log(`- ${name}`))
74
+ process.exit(1)
75
+ }
76
+
77
+ (async function() {
78
+ await db.connect(false)
79
+ await scripts[scriptName]()
80
+ await db.disconnect()
81
+ })()
package/bin/import.js ADDED
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env node
2
+
3
+ import meow from "meow"
4
+ const cli = meow(`
5
+ Usage
6
+ $ npm run import -- [options] [type input]
7
+
8
+ type and input are required unless with options --indexes.
9
+ input can be a .json file (single object or array), an .ndjson file (newline delimited JSON),
10
+ or a URL referring to a JSON or NDJSON source.
11
+
12
+ Note that with a URL, it either has to have a proper file ending or provide the correct content type
13
+ (i.e. application/json or application/x-ndjson). If not, the --format option has to be provided.
14
+
15
+ Options
16
+ GNU long option Option Meaning
17
+ --indexes -i Create indexes without import (can also be used in addition to import)
18
+ --quiet -q Only output warnings and errors
19
+ --format -f Either json or ndjson. Defaults to file ending or content type if available.
20
+ --scheme -s Only for concepts. Adds imported concepts to a scheme for specified URI.
21
+ The scheme must already exist. (topConceptOf still needs to be set if applicable.)
22
+ --concordance -c Only for mappings. Adds imported mappings into a concordance for specified URI.
23
+ The concordance must already exist.
24
+ --noreplace -n EXPERIMENTAL. When given, bulk writing will use insertOne instead of replaceOne,
25
+ meaning that existing entities will not be overridden.
26
+ Note that an error will be thrown when even one of the entities already exist.
27
+ --nobulk -b Disable bulk import no not ignore and filter out invalid entities
28
+ --set-api EXPERIMENTAL. Onlt for concepts. Will update the scheme's \`API\` property after
29
+ importing concepts.
30
+
31
+ Examples
32
+ $ npm run import -- --indexes
33
+ $ npm run import -- schemes schemes.ndjson
34
+ $ npm run import -- concepts concepts.ndjson
35
+ $ npm run import -- registry registries.ndjson
36
+ `, {
37
+ importMeta: import.meta,
38
+ flags: {
39
+ reset: {
40
+ type: "boolean",
41
+ shortFlag: "r",
42
+ default: false,
43
+ },
44
+ indexes: {
45
+ type: "boolean",
46
+ shortFlag: "i",
47
+ default: false,
48
+ },
49
+ quiet: {
50
+ type: "boolean",
51
+ shortFlag: "q",
52
+ default: false,
53
+ },
54
+ format: {
55
+ type: "string",
56
+ shortFlag: "f",
57
+ default: "",
58
+ },
59
+ scheme: {
60
+ type: "string",
61
+ shortFlag: "s",
62
+ default: "",
63
+ },
64
+ setApi: {
65
+ type: "boolean",
66
+ default: false,
67
+ },
68
+ concordance: {
69
+ type: "string",
70
+ shortFlag: "c",
71
+ default: "",
72
+ },
73
+ noreplace: {
74
+ type: "boolean",
75
+ shortFlag: "n",
76
+ default: false,
77
+ },
78
+ nobulk: {
79
+ type: "boolean",
80
+ shortFlag: "b",
81
+ default: false,
82
+ },
83
+ help: {
84
+ type: "boolean",
85
+ shortFlag: "h",
86
+ default: false,
87
+ },
88
+ },
89
+ })
90
+
91
+ const indexes = cli.flags.indexes
92
+
93
+ if (cli.flags.help || (!cli.input.length && !indexes)) {
94
+ cli.showHelp()
95
+ process.exit(0)
96
+ }
97
+
98
+ const log = (...args) => {
99
+ if (!cli.flags.quiet) {
100
+ console.log(...args)
101
+ }
102
+ }
103
+ const logError = ({ message, showHelp = false, exit = false }) => {
104
+ console.error(` Error: ${message}`)
105
+ if (showHelp) {
106
+ cli.showHelp()
107
+ }
108
+ if (exit) {
109
+ process.exit(1)
110
+ }
111
+ }
112
+
113
+ // --reset was removed
114
+ if (cli.flags.reset) {
115
+ logError({
116
+ message: "The option --reset was removed from the import script in version 1.2.0.",
117
+ showHelp: true,
118
+ exit: true,
119
+ })
120
+ // TODO: Mention URL to documentation and/or script that replaces --reset.
121
+ }
122
+
123
+ import jskos from "jskos-tools"
124
+ import fs from "node:fs"
125
+ const input = cli.input[1] || ""
126
+
127
+ // Parse type
128
+ const type = jskos.guessObjectType(cli.input[0], true)
129
+ if (cli.input[0] && !type) {
130
+ logError({
131
+ message: `Invalid <type> argument: ${cli.input[0] || ""}`,
132
+ showHelp: true,
133
+ exit: true,
134
+ })
135
+ }
136
+ if (!indexes && !type) {
137
+ logError({
138
+ message: "The <type> argument is necessary to import data.",
139
+ showHelp: true,
140
+ exit: true,
141
+ })
142
+ }
143
+ if (cli.flags.scheme && type != "concept") {
144
+ logError({
145
+ message: `The -s option is not compatible with type ${type}.`,
146
+ showHelp: true,
147
+ exit: true,
148
+ })
149
+ }
150
+ if (cli.flags.setApi && type != "concept") {
151
+ logError({
152
+ message: `The --set-api option is not compatible with type ${type}.`,
153
+ showHelp: true,
154
+ exit: true,
155
+ })
156
+ }
157
+ if (cli.flags.concordance && type != "mapping") {
158
+ logError({
159
+ message: `The -c option is not compatible with type ${type}.`,
160
+ showHelp: true,
161
+ exit: true,
162
+ })
163
+ }
164
+
165
+ if (cli.flags.bulk && ["concordance","concordance","registry"].find(type)) {
166
+ logError({
167
+ message: `The --nobulk option is not supported with type ${type}`,
168
+ exit: true,
169
+ })
170
+ }
171
+
172
+ // Check input parameter if necessary
173
+ const inputIsUrl = !!input.match(/^https?:\/\//)
174
+ if (!indexes) {
175
+ if (!input || (!inputIsUrl && !fs.existsSync(input))) {
176
+ logError({
177
+ message: `Invalid or missing <file>: ${input || ""}`,
178
+ showHelp: true,
179
+ exit: true,
180
+ })
181
+ }
182
+ }
183
+
184
+ // Check format parameter
185
+ let format
186
+ if (["json", "ndjson"].includes(cli.flags.format)) {
187
+ format = cli.flags.format
188
+ } else if (cli.flags.format) {
189
+ logError({
190
+ message: `Unknown format ${cli.flags.format}, please provide one of json or ndjson, or leave empty.`,
191
+ showHelp: true,
192
+ exit: true,
193
+ })
194
+ }
195
+
196
+ log(`Start of import script: ${new Date()}`)
197
+
198
+ // Log all parameters
199
+ input && log(`- will import ${type} from ${inputIsUrl ? "URL" : "local file"} ${input}`)
200
+ indexes && log("- will create indexes", type ? `for type ${type}` : "for all types")
201
+ format && log(`- with format: ${format}`)
202
+ log("")
203
+
204
+ import { validate } from "jskos-validate"
205
+ import config from "../config/index.js"
206
+ import { v5 as uuidv5 } from "uuid"
207
+ import path from "node:path"
208
+ import * as anystream from "json-anystream"
209
+ import * as db from "../utils/db.js"
210
+
211
+ import { createServices } from "../services/index.js"
212
+ const services = createServices(config)
213
+ const allTypes = Object.keys(services)
214
+
215
+ // Also import models for Mapping and Concordance
216
+ // TODO: This won't be needed if these are imported through the service as well.
217
+ import { Mapping, Concordance } from "../models/index.js"
218
+ import { bulkOperationForEntities, addMappingSchemes} from "../utils/utils.js"
219
+
220
+ ;(async () => {
221
+ try {
222
+ await db.connect()
223
+ } catch (error) {
224
+ logError({
225
+ message: error,
226
+ exit: true,
227
+ })
228
+ }
229
+ log("Connection to database established.")
230
+ log()
231
+
232
+ if (indexes) {
233
+ log("Creating indexes...")
234
+ let types = type ? [type] : allTypes
235
+ for (let type of types) {
236
+ await services[type].createIndexes()
237
+ log(`... done (${type})`)
238
+ }
239
+ log()
240
+ }
241
+
242
+ if (input) {
243
+ let concordance
244
+ if (cli.flags.concordance) {
245
+ // Query concordance from database
246
+ concordance = await services.concordance.retrieveItem(cli.flags.concordance)
247
+ if (!concordance) {
248
+ logError({
249
+ message: `Concordance with URI ${cli.flags.concordance} not found, aborting...`,
250
+ exit: true,
251
+ })
252
+ }
253
+ }
254
+ try {
255
+ await doImport({ input, format, type, concordance })
256
+ } catch (error) {
257
+ logError({ message: `Import failed - ${error}` })
258
+ }
259
+ }
260
+
261
+ db.disconnect()
262
+ log()
263
+ log(`End of import script: ${new Date()}`)
264
+ })()
265
+
266
+ async function doImport({ input, format, type, concordance }) {
267
+ const bodyStream = await anystream.make(input, format)
268
+ const bulk = !cli.flags.nobulk
269
+ const bulkReplace = !cli.flags.noreplace
270
+
271
+ if (type == "scheme") {
272
+ log("Importing schemes...")
273
+ const result = await services.scheme.createItem({ bodyStream, bulk, bulkReplace })
274
+ log(`... done: ${Array.isArray(result) ? result.length : 1} schemes imported.`)
275
+ } else if (type == "concept") {
276
+ log("Importing concepts...")
277
+ // TODO: Find way to output progress.
278
+ const result = await services.concept.createItem({
279
+ bodyStream, bulk, bulkReplace,
280
+ scheme: cli.flags.scheme,
281
+ setApi: cli.flags.setApi,
282
+ })
283
+ log(`... done: ${Array.isArray(result) ? result.length : 1} concepts imported.`)
284
+ } else if (type == "mapping") {
285
+ // TODO: Eventually, this should also be done through the service.
286
+ let mappings = []
287
+ // Keep track of concordances that need to be adjusted
288
+ const concordanceUrisToAdjust = new Set()
289
+ if (concordance?.uri) {
290
+ concordanceUrisToAdjust.add(concordance.uri)
291
+ }
292
+ let imported = 0
293
+ let total = 0
294
+ const saveMappings = async (mappings) => {
295
+ const result = await Mapping.bulkWrite(bulkOperationForEntities({ entities: mappings, replace: !cli.flags.noreplace }))
296
+ imported += result.insertedCount + result.upsertedCount + result.modifiedCount
297
+ console.log(`... ${imported} done ...`)
298
+ }
299
+ // Name the loop
300
+ mappingLoop: for await (let object of bodyStream) {
301
+ total += 1
302
+ if (!validate[type] || !validate[type](object)) {
303
+ logError({ message: `Could not validate ${type} number ${total}: ${object && object.uri}` })
304
+ continue
305
+ }
306
+ // Adjustments for mapping
307
+ if (object.uri) {
308
+ if (object.uri.startsWith(config.baseUrl)) {
309
+ object._id = object.uri.slice(object.uri.lastIndexOf("/") + 1)
310
+ } else {
311
+ // Put URI into identifier because it's not valid for this server
312
+ object.identifier = [].concat(object.identifier || [], object.uri)
313
+ delete object.uri
314
+ }
315
+ }
316
+ // Add reference to concordance.
317
+ if (concordance?.uri) {
318
+ object.partOf = [{
319
+ uri: concordance.uri,
320
+ }]
321
+ } else if (object.partOf?.[0]?.uri) {
322
+ concordanceUrisToAdjust.add(object.partOf?.[0]?.uri)
323
+ }
324
+ // Copy creator from concordance if it doesn't exist.
325
+ if (!object.creator && concordance && concordance.creator) {
326
+ object.creator = concordance.creator
327
+ }
328
+ // Set created of concordance if created is not set
329
+ if (!object.created && concordance && concordance.created) {
330
+ object.created = concordance.created
331
+ }
332
+ // Set modified if necessary
333
+ if (!object.modified && concordance && concordance.modified) {
334
+ object.modified = concordance.modified
335
+ }
336
+ if (!object.modified && object.created) {
337
+ object.modified = object.created
338
+ }
339
+ // Set fromScheme and toScheme from concordance
340
+ addMappingSchemes(object, { concordance })
341
+ // Check if schemes are available and replace them with URI/notation only
342
+ await services.scheme.replaceSchemeProperties(object, ["fromScheme", "toScheme"])
343
+ // Reject mapping if either fromScheme or toScheme is missing
344
+ for (let field of ["fromScheme", "toScheme"]) {
345
+ if (!object[field]) {
346
+ logError({ message: `Field \`${field}\` missing for ${type} number ${total}: ${object && object.uri}` })
347
+ continue mappingLoop
348
+ }
349
+ }
350
+ // Add mapping identifier
351
+ try {
352
+ object.identifier = jskos.addMappingIdentifiers(object).identifier
353
+ } catch (error) {
354
+ log("Could not add identifier to mapping.", error)
355
+ }
356
+ // Generate an identifier and a URI if necessary
357
+ if (!object.uri) {
358
+ const contentIdentifier = object.identifier.find(id => id && id.startsWith("urn:jskos:mapping:content:"))
359
+ const concordance = object.partOf?.[0]?.uri || ""
360
+ if (contentIdentifier) {
361
+ object._id = uuidv5(contentIdentifier + concordance, config.namespace)
362
+ object.uri = config.baseUrl + "mappings/" + object._id
363
+ }
364
+ }
365
+ // Write to mappings
366
+ mappings.push(object)
367
+ if (mappings.length % 5000 == 0) {
368
+ mappings.length && await saveMappings(mappings)
369
+ mappings = []
370
+ }
371
+ }
372
+ mappings.length && await saveMappings(mappings)
373
+ log(`... done: ${imported} mappings imported (${total - imported} skipped).`)
374
+ if (concordanceUrisToAdjust.size) {
375
+ log(`... adjusting extent for ${concordanceUrisToAdjust.size} concordances...`)
376
+ await Promise.all([...concordanceUrisToAdjust].map(uri => services.concordance.postAdjustmentForConcordance(uri)))
377
+ log("... done.")
378
+ }
379
+ } else if (type == "concordance") {
380
+ // TODO: Eventually, this should also be done through the service.
381
+ let imported = 0
382
+ let total = 0
383
+ for await (let concordance of bodyStream) {
384
+ total += 1
385
+ // Rewrite "distribution" to "distributions" if necessary
386
+ if (concordance.distribution) {
387
+ concordance.distributions = concordance.distribution
388
+ delete concordance.distribution
389
+ }
390
+ // Remove distributions with same baseUrl since they will be added dynamically
391
+ if (concordance.distributions) {
392
+ concordance.distributions = concordance.distributions.filter(dist => !dist.download || !dist.download.startsWith(config.baseUrl))
393
+ if (!concordance.distributions.length) {
394
+ delete concordance.distributions
395
+ }
396
+ }
397
+ // Validation
398
+ if (!validate[type] || !validate[type](concordance)) {
399
+ logError({ message: `Could not validate ${type} number ${total}: ${concordance && concordance.uri}` })
400
+ continue
401
+ }
402
+ const uri = concordance.uri
403
+ concordance._id = uri
404
+ const result = await Concordance.bulkWrite(bulkOperationForEntities({ entities: [concordance], replace: !cli.flags.noreplace }))
405
+ if (result.insertedCount + result.modifiedCount + result.upsertedCount === 1) {
406
+ imported += 1
407
+ log(`... imported concordance ${uri}, now importing its mappings...`)
408
+ // TODO: Should concordance be dropped?
409
+ let distribution = (concordance.distributions || []).find(element => element.mimetype.includes("json"))
410
+ if (distribution) {
411
+ // Build file URL
412
+ let url = ""
413
+ if (distribution.download.startsWith("http")) {
414
+ url = distribution.download
415
+ } else {
416
+ // ?
417
+ url = path.dirname(input) + "/" + distribution.download
418
+ }
419
+ await doImport({ input: url, type: "mapping", concordance })
420
+ } else {
421
+ log("... no mapping distribution found.")
422
+ }
423
+ }
424
+ }
425
+ log(`... done: ${imported} concordances imported (${total - imported} skipped).`)
426
+ } else if (type == "annotation") {
427
+ log("Importing annotations...")
428
+ // TODO: Find way to output progress.
429
+ const result = await services.annotation.createItem({ bodyStream, bulk, bulkReplace, admin: true })
430
+ log(`... done: ${Array.isArray(result) ? result.length : 1} annotations imported.`)
431
+ } else if (type == "registry") {
432
+ log("Importing registries...")
433
+ const result = await services.registry.createItem({ bodyStream, bulk, bulkReplace })
434
+ const imported = result?.importedCount ?? 0
435
+ const skipped = result?.skippedCount ?? 0
436
+ log(`... done: ${imported} registries imported${skipped > 0 ? ` (${skipped} skipped).` : "."}`)
437
+ }
438
+ }
package/bin/mongodb.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ import config from "../config/index.js"
4
+
5
+ if (process.argv.includes("--debug")) {
6
+ process.env.MONGOMS_DEBUG=1
7
+ }
8
+
9
+ // dynamic import, to take into account env
10
+ const { MongoMemoryServer, MongoMemoryReplSet } = await import("mongodb-memory-server")
11
+
12
+ const { port, db } = config.mongo
13
+ if (config.changes) {
14
+ const server = await MongoMemoryReplSet.create({ instanceOpts: [{ port }] })
15
+ console.log(`Started MongoDB replica set ${server.getUri()} database ${db}`)
16
+
17
+ } else {
18
+ const instance = { port, dbName: db }
19
+ const server = await MongoMemoryServer.create({ instance, replSet: { dbName: db } })
20
+ console.log(`Started MongoDB ${server.getUri()} database ${db}`)
21
+ }