aiiinotate 0.2.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 (88) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +61 -0
  3. package/cli/import.js +142 -0
  4. package/cli/index.js +26 -0
  5. package/cli/io.js +105 -0
  6. package/cli/migrate.js +123 -0
  7. package/cli/mongoClient.js +11 -0
  8. package/docs/architecture.md +88 -0
  9. package/docs/db.md +38 -0
  10. package/docs/dev_iiif_compatibility.md +43 -0
  11. package/docs/endpoints.md +48 -0
  12. package/docs/progress.md +159 -0
  13. package/docs/specifications/0_w3c_open_annotations.md +332 -0
  14. package/docs/specifications/1_w3c_web_annotations.md +577 -0
  15. package/docs/specifications/2_iiif_apis.md +396 -0
  16. package/docs/specifications/3_iiif_annotations.md +103 -0
  17. package/docs/specifications/4_search_api.md +135 -0
  18. package/docs/specifications/5_sas.md +119 -0
  19. package/docs/specifications/6_mirador.md +119 -0
  20. package/docs/specifications/7_aikon.md +137 -0
  21. package/docs/specifications/include/presentation_2.0.webp +0 -0
  22. package/docs/specifications/include/presentation_2.0_white.png +0 -0
  23. package/docs/specifications/include/presentation_3.0.png +0 -0
  24. package/docs/specifications/include/presentation_3.0_resize.png +0 -0
  25. package/eslint.config.js +27 -0
  26. package/migrations/baseConfig.js +56 -0
  27. package/migrations/manageIndex.js +55 -0
  28. package/migrations/migrate-mongo-config-main.js +8 -0
  29. package/migrations/migrate-mongo-config-test.js +8 -0
  30. package/migrations/migrationScripts/20250825185706-collections.js +41 -0
  31. package/migrations/migrationScripts/20250826194832-annotations2-canvas-index.js +31 -0
  32. package/migrations/migrationScripts/20250904080710-annotations2-schema.js +42 -0
  33. package/migrations/migrationScripts/20251002141951-manifest2-schema.js +43 -0
  34. package/migrations/migrationScripts/20251006212110-manifest-unique-index.js +29 -0
  35. package/migrations/migrationScripts/20251028115614-annotations2-id-index.js +27 -0
  36. package/migrations/migrationTemplate.js +25 -0
  37. package/package.json +78 -0
  38. package/run.sh +70 -0
  39. package/scripts/_migrations.sh +79 -0
  40. package/scripts/_setup.js +31 -0
  41. package/scripts/setup_mongodb.sh +61 -0
  42. package/scripts/setup_mongodb_migrate.sh +17 -0
  43. package/scripts/setup_node.sh +15 -0
  44. package/scripts/utils.sh +192 -0
  45. package/setup.sh +20 -0
  46. package/src/app.js +113 -0
  47. package/src/config/.env.template +22 -0
  48. package/src/data/annotations/annotations2.js +419 -0
  49. package/src/data/annotations/annotations3.js +32 -0
  50. package/src/data/annotations/routes.js +271 -0
  51. package/src/data/annotations/routes.test.js +180 -0
  52. package/src/data/collectionAbstract.js +270 -0
  53. package/src/data/index.js +29 -0
  54. package/src/data/manifests/manifests2.js +305 -0
  55. package/src/data/manifests/manifests2.test.js +53 -0
  56. package/src/data/manifests/manifests3.js +23 -0
  57. package/src/data/manifests/routes.js +95 -0
  58. package/src/data/manifests/routes.test.js +69 -0
  59. package/src/data/routes.js +141 -0
  60. package/src/data/routes.test.js +117 -0
  61. package/src/data/utils/iiif2Utils.js +196 -0
  62. package/src/data/utils/iiif2Utils.test.js +98 -0
  63. package/src/data/utils/iiif3Utils.js +0 -0
  64. package/src/data/utils/iiifUtils.js +18 -0
  65. package/src/data/utils/routeUtils.js +109 -0
  66. package/src/data/utils/testUtils.js +253 -0
  67. package/src/data/utils/utils.js +231 -0
  68. package/src/db/index.js +48 -0
  69. package/src/fileServer/annotations.js +39 -0
  70. package/src/fileServer/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
  71. package/src/fileServer/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
  72. package/src/fileServer/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
  73. package/src/fileServer/data/annotations2Invalid.jsonld +39 -0
  74. package/src/fileServer/data/annotations2Valid.jsonld +39 -0
  75. package/src/fileServer/data/bnf_invalid_manifest.json +2806 -0
  76. package/src/fileServer/data/bnf_valid_manifest.json +2817 -0
  77. package/src/fileServer/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
  78. package/src/fileServer/index.js +64 -0
  79. package/src/fileServer/manifests.js +14 -0
  80. package/src/fileServer/utils.js +35 -0
  81. package/src/schemas/index.js +20 -0
  82. package/src/schemas/schemasBase.js +47 -0
  83. package/src/schemas/schemasPresentation2.js +417 -0
  84. package/src/schemas/schemasPresentation3.js +57 -0
  85. package/src/schemas/schemasResolver.js +71 -0
  86. package/src/schemas/schemasRoutes.js +277 -0
  87. package/src/server.js +22 -0
  88. package/src/types.js +93 -0
@@ -0,0 +1,98 @@
1
+ import test from "node:test";
2
+
3
+ import build from "#src/app.js";
4
+ import { v4 as uuid4 } from "uuid";
5
+ import { getManifestShortId, getCanvasShortId, getAnnotationTarget, makeTarget, makeAnnotationId } from "#utils/iiif2Utils.js";
6
+
7
+
8
+ const
9
+ // hash-validating regex
10
+ hashRgx = /^\d+$/;
11
+
12
+ test("test 'iiif2Utils' functions", async (t) => {
13
+ const
14
+ fastify = await build("test"),
15
+ { annotations2Valid, annotations2Invalid } = fastify.fileServer;
16
+
17
+ await fastify.ready();
18
+
19
+ t.after(() => fastify.close());
20
+
21
+ t.test("test 'getManifestShortId'", (t) => {
22
+ const
23
+ s1 = `manifest_${uuid4()}`,
24
+ s2 = `extra_${uuid4()}`,
25
+ // urls for which the function will manage to extract `s1`
26
+ urlOk = [
27
+ `http://www.example.com/examplePrefix/${s1}/manifest`,
28
+ `http://www.example.com/examplePrefix/${s1}/manifest.json`,
29
+ `http://www.example.com/examplePrefix/${s1}/sequence/${s2}`,
30
+ `http://www.example.com/examplePrefix/${s1}/canvas/${s2}`,
31
+ `http://www.example.com/examplePrefix/${s1}/annotation/${s2}`,
32
+ `http://www.example.com/examplePrefix/${s1}/list/${s2}`,
33
+ `http://www.example.com/examplePrefix/${s1}/range/${s2}`,
34
+ `http://www.example.com/examplePrefix/${s1}/layer/${s2}`,
35
+ `http://www.example.com/examplePrefix/${s1}/res/${s2}.png`,
36
+ ],
37
+ // urls for which `s1` won't be returned, and instead a hash will be returned
38
+ urlHash = [
39
+ `http://example.com/example/collection/${s2}`,
40
+ `http://example.com/${s1}`
41
+ ];
42
+
43
+ urlOk.map((url) =>
44
+ t.assert.strictEqual(getManifestShortId(url), s1));
45
+ urlHash.map((url) =>
46
+ t.assert.strictEqual(hashRgx.test(getManifestShortId(url)), true));
47
+ })
48
+
49
+ t.test("test 'getCanvasShortId'", (t) => {
50
+ const
51
+ s1 = `manifest_${uuid4()}`,
52
+ s2 = `canvas_${uuid4()}`,
53
+ urlOk = [
54
+ `http://www.example.com/examplePrefix/${s1}/canvas/${s2}`,
55
+ `http://www.example.com/examplePrefix/${s1}/canvas/${s2}.json`,
56
+
57
+ ],
58
+ urlHash = [
59
+ `http://example.com/example/${s2}`,
60
+ `http://example.com/${s2}`
61
+ ]
62
+ urlOk.map((url) =>
63
+ t.assert.strictEqual(getCanvasShortId(url), s2));
64
+ urlHash.map((url) =>
65
+ t.assert.strictEqual(hashRgx.test(getCanvasShortId(url)), true));
66
+
67
+ })
68
+
69
+ t.test("test 'getAnnotationTarget' and 'makeTarget'", async (t) => {
70
+ await Promise.all(
71
+ [getAnnotationTarget, makeTarget].map((func) =>
72
+
73
+ t.test(`test '${func.name}'`, (t) => {
74
+ annotations2Valid.map((annotation) =>
75
+ // to test for an error, it's necessary to create a new function:
76
+ // https://stackoverflow.com/a/6645586
77
+ t.assert.doesNotThrow(() => func(annotation)));
78
+ annotations2Invalid.map((annotation) =>
79
+ t.assert.throws(() => func(annotation), Error));
80
+ })
81
+
82
+ )
83
+ )
84
+ })
85
+
86
+ t.test("test 'makeAnnotationId'", (t) => {
87
+ // https://stackoverflow.com/a/6969486
88
+ const escapeRegExp = (string) =>
89
+ string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
90
+
91
+ const rgx = new RegExp(`^${escapeRegExp(process.env.APP_BASE_URL)}/data/2/[^(\\s|/)]+/annotation/[^\\.]+$`);
92
+ annotations2Valid.map((annotation) =>
93
+ t.assert.strictEqual(rgx.test(makeAnnotationId(annotation)), true));
94
+ annotations2Invalid.map((annotation) =>
95
+ t.assert.throws(() => makeAnnotationId(annotation)));
96
+ })
97
+
98
+ })
File without changes
@@ -0,0 +1,18 @@
1
+ import { getHash } from "#utils/utils.js";
2
+
3
+ const IIIF_PRESENTATION_2 = 2;
4
+ const IIIF_PRESENTATION_3 = 3;
5
+ const IIIF_SEARCH_1 = 1;
6
+ const IIIF_SEARCH_2 = 2;
7
+ const IIIF_PRESENTATION_2_CONTEXT = { "@context": "http://iiif.io/api/presentation/2/context.json" };
8
+ const IIIF_PRESENTATION_3_CONTEXT = { "@context": "http://iiif.io/api/presentation/3/context.json" };
9
+
10
+
11
+ export {
12
+ IIIF_PRESENTATION_2,
13
+ IIIF_PRESENTATION_3,
14
+ IIIF_SEARCH_1,
15
+ IIIF_SEARCH_2,
16
+ IIIF_PRESENTATION_2_CONTEXT,
17
+ IIIF_PRESENTATION_3_CONTEXT,
18
+ }
@@ -0,0 +1,109 @@
1
+ import { inspectObj, isNonEmptyArray } from "#utils/utils.js";
2
+
3
+ /** @typedef {import("mongodb").UpdateResult} MongoUpdateResultType */
4
+ /** @typedef {import("#types").InsertResponseType} InsertResponseType */
5
+ /** @typedef {import("#types").UpdateResponseType} UpdateResponseType */
6
+ /** @typedef {import("#types").DeleteResponseType} DeleteResponseType */
7
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
8
+
9
+ /**
10
+ * functionnal alternative to `formatInsertResponse`, that aldready expects a formatted object
11
+ * @param {string[]?} insertedIds
12
+ * @param {string[]?} preExistingIds
13
+ * @param {string[]?} fetchErrorIds
14
+ * @param {string[]?} rejectedIds
15
+ * @returns {InsertResponseType}
16
+ */
17
+ const formatInsertResponse = (insertedIds, preExistingIds, fetchErrorIds, rejectedIds) => {
18
+ const out = {
19
+ insertedCount: insertedIds?.length || 0,
20
+ insertedIds: insertedIds || [],
21
+ };
22
+ if ( fetchErrorIds?.length ) {
23
+ out.fetchErrorIds = fetchErrorIds;
24
+ }
25
+ if ( rejectedIds?.length ) {
26
+ out.rejectedIds = rejectedIds;
27
+ }
28
+ if ( preExistingIds?.length ) {
29
+ out.preExistingIds = preExistingIds;
30
+ };
31
+ return out;
32
+ }
33
+
34
+ /**
35
+ * @param {UpdateResponseType} mongoRes
36
+ * @returns {UpdateResponseType}
37
+ */
38
+ const formatUpdateResponse = (mongoRes) => ({
39
+ matchedCount: mongoRes.matchedCount,
40
+ modifiedCount: mongoRes.modifiedCount,
41
+ upsertedCount: mongoRes.upsertedCount,
42
+ upsertedId: mongoRes.upsertedId
43
+ });
44
+
45
+ /**
46
+ * @param {DeleteResponseType} mongoRes
47
+ * @returns {DeleteResponseType}
48
+ */
49
+ const formatDeleteResponse = (mongoRes) => ({
50
+ deletedCount: mongoRes.deletedCount
51
+ });
52
+
53
+ /**
54
+ * NOTE: fastify only implements top-level `$ref` in responses, using $ref in response schemas is not allowed.
55
+ * in turn, we can't use `{ $ref: makeSchemaUri }` and must resolve schemas instead.
56
+ * @param {FastifyInstanceType} fastify
57
+ * @param {object} okResponseSchema - expected response schema
58
+ */
59
+ const makeResponseSchema = (fastify, okResponseSchema) => ({
60
+ 200: okResponseSchema,
61
+ 500: fastify.schemasRoutes.getSchema("routeResponseError")
62
+ })
63
+
64
+ const makeResponsePostSchema = (fastify) => makeResponseSchema(
65
+ fastify,
66
+ {
67
+ anyOf: [
68
+ fastify.schemasRoutes.getSchema("routeResponseInsert"),
69
+ fastify.schemasRoutes.getSchema("routeResponseUpdate"),
70
+ fastify.schemasRoutes.getSchema("routeResponseDelete"),
71
+ ]
72
+ }
73
+ );
74
+
75
+ /**
76
+ *
77
+ * @param {import("fastify").FastifyRequest} request
78
+ * @param {import("fastify").FastifyReply} reply
79
+ * @param {Error} err: the error we're returning
80
+ * @param {number} statusCode - the status code (defaults to 500)
81
+ * @param {any?} requestBody: the data on which the error occurred, for POST requests
82
+ */
83
+ const returnError = (request, reply, err, requestBody={}, statusCode=500) => {
84
+ // otherwise, the error is not logged, bad for debugging.
85
+ console.error(inspectObj(err));
86
+
87
+ const error = {
88
+ message: `failed ${request.method.toLocaleUpperCase()} request because of error: ${err.message}`,
89
+ info: err.info || {},
90
+ method: request.method,
91
+ url: request.url
92
+ };
93
+ if ( requestBody !== undefined ) {
94
+ error.requestBody = requestBody
95
+ }
96
+ reply
97
+ .status(statusCode)
98
+ .header("Content-Type", "application/json; charset=utf-8")
99
+ .send(error);
100
+ }
101
+
102
+ export {
103
+ formatInsertResponse,
104
+ formatUpdateResponse,
105
+ formatDeleteResponse,
106
+ makeResponsePostSchema,
107
+ makeResponseSchema,
108
+ returnError
109
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * utilities and generally useful functions for tests.
3
+ */
4
+
5
+ import { visibleLog } from "#utils/utils.js";
6
+
7
+ /** @typedef {import("#types").NodeTestType} NodeTestType */
8
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
9
+ /** @typedef {import("#types").FastifyReplyType} FastifyReplyType */
10
+
11
+ /**
12
+ * @param {NodeTestType} t
13
+ * @param {object} obj
14
+ * @param {Array} expectedKeys
15
+ * @returns {void}
16
+ */
17
+ const assertObjectKeys = (t, obj, expectedKeys) =>
18
+ t.assert.deepStrictEqual(
19
+ Object.keys(obj).sort(),
20
+ expectedKeys.sort()
21
+ );
22
+
23
+ /**
24
+ * insert responses MUST contain `insertedCount` and MAY contain ["insertedIds", "preExistingIds", "fetchErrorIds", "rejectedIds"]
25
+ * @param {NodeTestType} t
26
+ * @param {object} obj
27
+ * @returns {void}
28
+ */
29
+ const assertObjectKeysInsert = (t, obj) => {
30
+ t.assert.deepStrictEqual(
31
+ Object.keys(obj).every((k) => ["insertedCount", "insertedIds", "preExistingIds", "fetchErrorIds", "rejectedIds"].includes(k)),
32
+ true
33
+ );
34
+ t.assert.deepStrictEqual(
35
+ Object.keys(obj).includes("insertedCount"), true
36
+ )
37
+ }
38
+
39
+ /**
40
+ * @param {NodeTestType} t
41
+ * @param {object} obj
42
+ * @returns {void}
43
+ */
44
+ const assertObjectKeysUpdate = (t, obj) =>
45
+ assertObjectKeys(t, obj, ["matchedCount", "modifiedCount", "upsertedCount", "upsertedId"]);
46
+
47
+ /**
48
+ * @param {NodeTestType} t
49
+ * @param {object} obj
50
+ * @returns {void}
51
+ */
52
+ const assertObjectKeysDelete = (t, obj) =>
53
+ assertObjectKeys(t, obj, ["deletedCount"]);
54
+
55
+ /**
56
+ * @param {NodeTestType} t
57
+ * @param {object} obj
58
+ * @returns {void}
59
+ */
60
+ const assertObjectKeysError = (t, obj) =>
61
+ assertObjectKeys(t, obj, ["message", "info", "method", "url", "requestBody"]);
62
+
63
+ /**
64
+ * @param {NodeTestType} t
65
+ * @param {FastifyReplyType} r
66
+ * @param {number} expectedStatusCode
67
+ * @returns {void}
68
+ */
69
+ const assertStatusCode = (t, r, expectedStatusCode) =>
70
+ t.assert.deepStrictEqual(r.statusCode, expectedStatusCode);
71
+
72
+ /**
73
+ * @param {NodeTestType} t
74
+ * @param {FastifyReplyType} r
75
+ * @param {"insert"|"update"|"delete"|"error"} expectedResponse: keyword to define the response schema to test against.
76
+ * @returns {void}
77
+ */
78
+ const assertResponseKeys = (t, r, expectedResponse) =>
79
+ expectedResponse === "insert"
80
+ ? assertObjectKeysInsert(t, JSON.parse(r.body))
81
+ : expectedResponse === "update"
82
+ ? assertObjectKeysUpdate(t, JSON.parse(r.body))
83
+ : expectedResponse === "delete"
84
+ ? assertObjectKeysDelete(t, JSON.parse(r.body))
85
+ : assertObjectKeysError(t, JSON.parse(r.body));
86
+
87
+ /**
88
+ * @param {FastifyInstanceType} fastify
89
+ * @param {string} route
90
+ * @param {object} payload
91
+ * @returns {Promise<FastifyReplyType>}
92
+ */
93
+ const injectPost = (fastify, route, payload) =>
94
+ fastify.inject({
95
+ method: "POST",
96
+ url: route,
97
+ payload: payload,
98
+ });
99
+
100
+ /**
101
+ * @param {NodeTestType} t
102
+ * @param {FastifyReplyType} r
103
+ * @returns {void}
104
+ */
105
+ const assertPostInvalidResponse = (t, r) => {
106
+ assertStatusCode(t, r, 500);
107
+ assertResponseKeys(t, r, "error");
108
+ }
109
+
110
+ /**
111
+ * @param {NodeTestType} t
112
+ * @param {FastifyReplyType} r
113
+ * @returns {void}
114
+ */
115
+ const assertCreateValidResponse = (t, r) => {
116
+ assertStatusCode(t, r, 200);
117
+ assertResponseKeys(t, r, "insert");
118
+ }
119
+
120
+ /**
121
+ * @param {NodeTestType} t
122
+ * @param {FastifyReplyType} r
123
+ * @returns {void}
124
+ */
125
+ const assertUpdateValidResponse = (t,r) => {
126
+ assertStatusCode(t, r, 200);
127
+ assertResponseKeys(t, r, "update");
128
+ }
129
+
130
+ /**
131
+ * @param {NodeTestType} t
132
+ * @param {FastifyReplyType} r
133
+ * @returns {void}
134
+ */
135
+ const assertDeleteValidResponse = (t,r) => {
136
+ assertStatusCode(t, r, 200);
137
+ assertResponseKeys(t, r, "delete");
138
+ }
139
+
140
+ /**
141
+ * @param {NodeTestType} t
142
+ * @param {FastifyReplyType} r
143
+ * @param {number} expectedStatusCode
144
+ */
145
+ const assertErrorValidResponse = (t, r, expectedStatusCode=500) => {
146
+ assertObjectKeysError(t, JSON.parse(r.body));
147
+ assertStatusCode(t, r, expectedStatusCode);
148
+ };
149
+
150
+ /**
151
+ * curried function to test different operations with a post route.
152
+ * @param {FastifyInstanceType} fastify
153
+ */
154
+ const testPostRouteCurry = (fastify) =>
155
+ /** @param {DataOperationsType} op */
156
+ (op) =>
157
+ /** @param {boolean} success - if `true` test that the query succeeds. else, test that it fails */
158
+ (success) =>
159
+ /**
160
+ * @param {NodeTestType} t
161
+ * @param {string} route: example: /annotations/2/createMany
162
+ * @param {object} payload
163
+ */
164
+ async (t, route, payload) => {
165
+ const
166
+ r = await injectPost(fastify, route, payload),
167
+ funcInvalid = assertPostInvalidResponse;
168
+
169
+ let funcValid;
170
+ if ( op==="insert" ) {
171
+ funcValid = assertCreateValidResponse;
172
+ } else if ( op==="update" ) {
173
+ funcValid = assertUpdateValidResponse;
174
+ } else {
175
+ throw new Error(`testPostRouteCurry: unimplemented value of 'op': '${op}'.`)
176
+ }
177
+
178
+ success
179
+ ? funcValid(t, r)
180
+ : funcInvalid(t, r);
181
+ return;
182
+ }
183
+
184
+ const testDeleteRouteCurry =
185
+ /** @param {FastifyInstanceType} */
186
+ (fastify) =>
187
+ /**
188
+ * @param {NodeTestType} t
189
+ * @param {string} deleteRoute - route to delete data, with delete parameters embedded
190
+ * @param {number} expectedDeletedCount - number of documents that should be deleted
191
+ */
192
+ async (t, deleteRoute, expectedDeletedCount) => {
193
+ const r = await fastify.inject({
194
+ method: "DELETE",
195
+ url: deleteRoute
196
+ })
197
+ assertDeleteValidResponse(t, r);
198
+ t.assert.deepStrictEqual(JSON.parse(r.body).deletedCount, expectedDeletedCount);
199
+ }
200
+
201
+ /**
202
+ * inject a manifest into the database for test purposes
203
+ * @param {FastifyInstanceType} fastify
204
+ * @param {NodeTestType} t
205
+ * @param {object} manifest - the manifest to insert
206
+ * @returns {Promise<Array<number, Array<string>>>}
207
+ */
208
+ const injectTestManifest = async (fastify, t, manifest) => {
209
+ const
210
+ r = await injectPost(fastify, "/manifests/2/create", manifest),
211
+ { insertedCount, insertedIds } = JSON.parse(r.body);
212
+ t.assert.deepStrictEqual(insertedCount, 1);
213
+ return [ insertedCount, insertedIds ];
214
+ }
215
+
216
+ /**
217
+ * inject an annotationList into the database for test purposes
218
+ * @param {FastifyInstanceType} fastify
219
+ * @param {NodeTestType} t
220
+ * @param {object} annotationList - a IIIF 2.x annotation list
221
+ * @returns {Promise<Array<number, Array<string>>>}
222
+ */
223
+ const injectTestAnnotations = async (fastify, t, annotationList) => {
224
+ const
225
+ r = await injectPost(fastify, "/annotations/2/createMany", annotationList),
226
+ rBody = JSON.parse(r.body),
227
+ expectedInsertedCount = annotationList.resources.length,
228
+ { insertedCount, insertedIds } = rBody;
229
+ t.assert.deepStrictEqual(insertedCount, expectedInsertedCount);
230
+ return [insertedCount, insertedIds];
231
+ }
232
+
233
+
234
+ export {
235
+ assertObjectKeys,
236
+ assertObjectKeysError,
237
+ assertObjectKeysInsert,
238
+ assertObjectKeysUpdate,
239
+ assertObjectKeysDelete,
240
+ assertStatusCode,
241
+ assertResponseKeys,
242
+ assertErrorValidResponse,
243
+ injectPost,
244
+ assertPostInvalidResponse,
245
+ assertCreateValidResponse,
246
+ assertUpdateValidResponse,
247
+ assertDeleteValidResponse,
248
+ testPostRouteCurry,
249
+ testDeleteRouteCurry,
250
+ injectTestManifest,
251
+ injectTestAnnotations
252
+ }
253
+