@paulhectork/aiiinotate 0.12.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 (104) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +206 -0
  3. package/cli/export.js +118 -0
  4. package/cli/import.js +111 -0
  5. package/cli/index.js +45 -0
  6. package/cli/migrate.js +131 -0
  7. package/cli/serve.js +29 -0
  8. package/cli/utils/fastifyClient.js +86 -0
  9. package/cli/utils/io.js +225 -0
  10. package/cli/utils/mongoClient.js +21 -0
  11. package/cli/utils/progressbar.js +86 -0
  12. package/cli/xywhToInt.js +99 -0
  13. package/config/.env.template +48 -0
  14. package/docker/Dockerfile +39 -0
  15. package/docker/README.md +5 -0
  16. package/docker/docker-compose.yaml +49 -0
  17. package/docker/docker.sh +43 -0
  18. package/docker/docker_aiiinotate_import.sh +62 -0
  19. package/docs/api.md +381 -0
  20. package/docs/cli.md +132 -0
  21. package/docs/dev_documentation/dev_architecture.md +88 -0
  22. package/docs/dev_documentation/dev_db.md +58 -0
  23. package/docs/dev_documentation/dev_iiif_compatibility.md +43 -0
  24. package/docs/dev_documentation/dev_notes_quirks_and_troubleshooting.md +143 -0
  25. package/docs/docker.md +98 -0
  26. package/docs/includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png +0 -0
  27. package/docs/scalability.md +34 -0
  28. package/docs/specifications/0_w3c_open_annotations.md +332 -0
  29. package/docs/specifications/1_w3c_web_annotations.md +577 -0
  30. package/docs/specifications/2_iiif_apis.md +428 -0
  31. package/docs/specifications/3_iiif_annotations.md +103 -0
  32. package/docs/specifications/4_search_api.md +135 -0
  33. package/docs/specifications/5_sas.md +119 -0
  34. package/docs/specifications/6_mirador.md +119 -0
  35. package/docs/specifications/7_aikon.md +137 -0
  36. package/docs/specifications/include/presentation_2.0.webp +0 -0
  37. package/docs/specifications/include/presentation_2.0_white.png +0 -0
  38. package/docs/specifications/include/presentation_3.0.png +0 -0
  39. package/docs/specifications/include/presentation_3.0_resize.png +0 -0
  40. package/eslint.config.js +30 -0
  41. package/migrations/baseConfig.js +57 -0
  42. package/migrations/manageIndex.js +55 -0
  43. package/migrations/migrate-mongo-config-main.js +8 -0
  44. package/migrations/migrate-mongo-config-test.js +8 -0
  45. package/migrations/migrationScripts/20250825185706-collections.js +48 -0
  46. package/migrations/migrationScripts/20250826194832-annotations2-schema.js +42 -0
  47. package/migrations/migrationScripts/20250904080710-annotations2-indexes.js +69 -0
  48. package/migrations/migrationScripts/20251002141951-manifests2-schema.js +43 -0
  49. package/migrations/migrationScripts/20251006212110-manifests2-indexes.js +35 -0
  50. package/migrations/migrationTemplate.js +25 -0
  51. package/package.json +82 -0
  52. package/scripts/get_version.py +12 -0
  53. package/scripts/run.sh +36 -0
  54. package/scripts/setup_mongodb.sh +70 -0
  55. package/scripts/setup_node.sh +15 -0
  56. package/scripts/update_version.py +30 -0
  57. package/scripts/utils.sh +65 -0
  58. package/src/app.js +116 -0
  59. package/src/constants.js +73 -0
  60. package/src/data/annotations/annotations2.js +681 -0
  61. package/src/data/annotations/annotations3.js +28 -0
  62. package/src/data/annotations/routes.js +335 -0
  63. package/src/data/annotations/routes.test.js +271 -0
  64. package/src/data/collectionAbstract.js +283 -0
  65. package/src/data/index.js +29 -0
  66. package/src/data/manifests/manifests2.js +378 -0
  67. package/src/data/manifests/manifests2.test.js +53 -0
  68. package/src/data/manifests/manifests3.js +23 -0
  69. package/src/data/manifests/routes.js +122 -0
  70. package/src/data/manifests/routes.test.js +70 -0
  71. package/src/data/routes.js +181 -0
  72. package/src/data/routes.test.js +166 -0
  73. package/src/db/index.js +50 -0
  74. package/src/fixtures/annotations.js +41 -0
  75. package/src/fixtures/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
  76. package/src/fixtures/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
  77. package/src/fixtures/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
  78. package/src/fixtures/data/annotations2Invalid.jsonld +39 -0
  79. package/src/fixtures/data/annotations2SvgValid.jsonld +81 -0
  80. package/src/fixtures/data/annotations2Valid.jsonld +39 -0
  81. package/src/fixtures/data/bnf_invalid_manifest.json +2806 -0
  82. package/src/fixtures/data/bnf_valid_manifest.json +2817 -0
  83. package/src/fixtures/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
  84. package/src/fixtures/generate.js +181 -0
  85. package/src/fixtures/index.js +69 -0
  86. package/src/fixtures/manifests.js +14 -0
  87. package/src/fixtures/utils.js +37 -0
  88. package/src/schemas/index.js +100 -0
  89. package/src/schemas/schemasBase.js +19 -0
  90. package/src/schemas/schemasPresentation2.js +410 -0
  91. package/src/schemas/schemasPresentation3.js +33 -0
  92. package/src/schemas/schemasResolver.js +71 -0
  93. package/src/schemas/schemasRoutes.js +318 -0
  94. package/src/server.js +25 -0
  95. package/src/types.js +97 -0
  96. package/src/utils/iiif2Utils.js +332 -0
  97. package/src/utils/iiif2Utils.test.js +146 -0
  98. package/src/utils/iiif3Utils.js +0 -0
  99. package/src/utils/iiifUtils.js +18 -0
  100. package/src/utils/logger.js +119 -0
  101. package/src/utils/routeUtils.js +137 -0
  102. package/src/utils/svg.js +417 -0
  103. package/src/utils/testUtils.js +289 -0
  104. package/src/utils/utils.js +403 -0
@@ -0,0 +1,318 @@
1
+ /**
2
+ * schemas for routes. these schemas are more permissive than the database schemas, defined for example in `presentation2`.
3
+ */
4
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
5
+
6
+ function addSchemas(fastify, makeSchemaUri) {
7
+
8
+ ////////////////////////////////////////////////////////
9
+ // ANNOTATIONS ROUTES
10
+
11
+ fastify.addSchema({
12
+ $id: makeSchemaUri("routeAnnotation2"),
13
+ type: "object",
14
+ required: [ "@id", "@type", "motivation", "on" ],
15
+ properties: {
16
+ "@id": { type: "string" },
17
+ "@type": { type: "string" },
18
+ motivation: { anyOf: [
19
+ { type: "string", enum: [ "oa:Annotation" ] },
20
+ { type: "array", items: { type: "string" } }
21
+ ] },
22
+ on: {
23
+ anyOf: [
24
+ { type: "string" },
25
+ { type: "object" },
26
+ {
27
+ type: "array",
28
+ items: { anyOf: [
29
+ { type: "string" },
30
+ { type: "object" }
31
+ ] },
32
+ minItems: 1
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ });
38
+
39
+ fastify.addSchema({
40
+ $id: makeSchemaUri("routeAnnotation3"),
41
+ type: "object",
42
+ required: [ "id", "type", "motivation", "target" ],
43
+ properties: {
44
+ id: { type: "string" },
45
+ type: { type: "string", enum: [ "Annotation" ] },
46
+ motivation: { anyOf: [
47
+ { type: "string" },
48
+ { type: "array", items: { type: "string" } },
49
+ ] },
50
+ target: {
51
+ anyOf: [
52
+ { type: "string" },
53
+ { type: "object" },
54
+ {
55
+ type: "array",
56
+ items: { anyOf: [
57
+ { type: "string" },
58
+ { type: "object" }
59
+ ] },
60
+ minItems: 1
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ })
66
+
67
+ fastify.addSchema({
68
+ $id: makeSchemaUri("routeAnnotation2Or3"),
69
+ anyOf: [
70
+ { $ref: makeSchemaUri("routeAnnotation2") },
71
+ { $ref: makeSchemaUri("routeAnnotation3") }
72
+ ]
73
+ });
74
+
75
+ fastify.addSchema({
76
+ $id: makeSchemaUri("routeAnnotationListOrPageUri"),
77
+ type: "object",
78
+ required: [ "uri" ],
79
+ properties: {
80
+ "uri": { type: "string" },
81
+ }
82
+ });
83
+
84
+ fastify.addSchema({
85
+ $id: makeSchemaUri("routeAnnotationListOrPageUriArray"),
86
+ type: "array",
87
+ items: { $ref: "routeAnnotationListOrPageUri" }
88
+ });
89
+
90
+ fastify.addSchema({
91
+ $id: makeSchemaUri("routeAnnotationList"),
92
+ type: "object",
93
+ required: [ "@id", "@type", "resources" ],
94
+ properties: {
95
+ "@context": { type: "string" }, // i don't specify the value because @context may be an URI that points to a JSON that contains several namespaces other than "http://iiif.io/api/presentation/2/context.json"
96
+ "@id": { type: "string" },
97
+ "@type": { type: "string", enum: [ "sc:AnnotationList" ] },
98
+ "resources": {
99
+ type: "array",
100
+ items: { $ref: makeSchemaUri("routeAnnotation2") }
101
+ }
102
+ }
103
+ })
104
+
105
+ fastify.addSchema({
106
+ $id: makeSchemaUri("routeAnnotationPage"),
107
+ type: "object",
108
+ required: [ "@id", "@type", "items" ],
109
+ properties: {
110
+ "@context": { type: "string" }, // i don't specify the value because @context may be an URI that points to a JSON that contains several namespaces other than "http://iiif.io/api/presentation/2/context.json"
111
+ "id": { type: "string" },
112
+ "type": { type: "string", enum: [ "AnnotationPage" ] },
113
+ "items": {
114
+ type: "array",
115
+ items: { $ref: makeSchemaUri("routeAnnotation3") }
116
+ }
117
+ }
118
+ });
119
+
120
+ fastify.addSchema({
121
+ $id: makeSchemaUri("routeAnnotationListArray"),
122
+ type: "array",
123
+ items: { $ref: makeSchemaUri("routeAnnotationList") }
124
+ });
125
+
126
+ fastify.addSchema({
127
+ $id: makeSchemaUri("routeAnnotationPageArray"),
128
+ type: "array",
129
+ items: { $ref: makeSchemaUri("routeAnnotationPage") }
130
+ })
131
+
132
+ fastify.addSchema({
133
+ $id: makeSchemaUri("routeAnnotationCreateMany"),
134
+ anyOf: [
135
+ { $ref: makeSchemaUri("routeAnnotationList") },
136
+ { $ref: makeSchemaUri("routeAnnotationPage") },
137
+ { $ref: makeSchemaUri("routeAnnotationListArray") },
138
+ { $ref: makeSchemaUri("routeAnnotationPageArray") },
139
+ { $ref: makeSchemaUri("routeAnnotationListOrPageUri") },
140
+ { $ref: makeSchemaUri("routeAnnotationListOrPageUriArray") },
141
+ ]
142
+ })
143
+
144
+ // to delete an annotation by tag, you must also provide a manifestShortId (to avoid deleting all annotations with this tag in the entire db)
145
+ fastify.addSchema({
146
+ $id: makeSchemaUri("routeAnnotationFilterTag"),
147
+ type: "object",
148
+ required: [ "tag", "manifestShortId" ],
149
+ properties: {
150
+ manifestShortId: {
151
+ type: "string", description: "delete all annotations for a single manifest"
152
+ },
153
+ tag: {
154
+ type: "string", description: "delete allannotations for a single tag"
155
+ }
156
+ },
157
+ additionalProperties: false
158
+ })
159
+
160
+ // for annotations: key-value pairs to filter a manifests collection by
161
+ fastify.addSchema({
162
+ $id: makeSchemaUri("routeAnnotationFilter"),
163
+ oneOf: [
164
+ {
165
+ type: "object",
166
+ required: [ "uri" ],
167
+ properties: { uri: { type: "string", description: "delete the annotation with this '@id'" } },
168
+ additionalProperties: false
169
+ },
170
+ {
171
+ type: "object",
172
+ required: [ "manifestShortId" ],
173
+ properties: { manifestShortId: { type: "string", description: "delete all annotations for a single manifest" } },
174
+ additionalProperties: false
175
+ },
176
+ {
177
+ type: "object",
178
+ required: [ "canvasUri" ],
179
+ properties: { canvasUri: { type: "string", description: "delete all annotations for a single canvas" } },
180
+ additionalProperties: false
181
+ }
182
+ ]
183
+ })
184
+
185
+ // delete either by tag+manifestShortId, or by annotation URI, manifestShortId, canvasUri
186
+ fastify.addSchema({
187
+ $id: makeSchemaUri("routeAnnotationDeleteFilter"),
188
+ oneOf: [
189
+ { $ref: makeSchemaUri("routeAnnotationFilterTag") },
190
+ { $ref: makeSchemaUri("routeAnnotationFilter") },
191
+ ]
192
+ })
193
+
194
+ ////////////////////////////////////////////////////////
195
+ // MANIFESTS ROUTES
196
+
197
+ fastify.addSchema({
198
+ $id: makeSchemaUri("routeManifest2Or3"),
199
+ anyOf: [
200
+ {
201
+ type: "object",
202
+ required: [ "uri" ],
203
+ properties: { uri: { type: "string" } }
204
+ },
205
+ { $ref: fastify.schemasPresentation2.makeSchemaUri("manifestPublic") },
206
+ { $ref: fastify.schemasPresentation3.makeSchemaUri("manifestPublic") },
207
+ ]
208
+ });
209
+
210
+ // for manifests: key-value pairs to filter a manifests collection by
211
+ fastify.addSchema({
212
+ $id: makeSchemaUri("routeManifestFilter"),
213
+ type: "object",
214
+ oneOf: [
215
+ {
216
+ type: "object",
217
+ required: [ "uri" ],
218
+ properties: { uri: { type: "string" } },
219
+ additionalProperties: false
220
+ },
221
+ {
222
+ type: "object",
223
+ required: [ "manifestShortId" ],
224
+ properties: { manifestShortId: { type: "string" } },
225
+ additionalProperties: false
226
+ }
227
+ ]
228
+ });
229
+
230
+ ////////////////////////////////////////////////////////
231
+ // GENERIC STUFF
232
+
233
+ fastify.addSchema({
234
+ $id: makeSchemaUri("routeDelete"),
235
+ type: "object",
236
+ oneOf: [
237
+ { $ref: makeSchemaUri("routeAnnotationFilter") },
238
+ { $ref: makeSchemaUri("routeManifestFilter") },
239
+ ]
240
+ });
241
+
242
+ fastify.addSchema({
243
+ $id: makeSchemaUri("routeResponseInsert"),
244
+ type: "object",
245
+ required: [ "insertedCount" ],
246
+ properties: {
247
+ insertedCount: { type: "integer", minimum: 0 },
248
+ insertedIds: {
249
+ type: "array",
250
+ items: { type: "string" }
251
+ },
252
+ preExistingIds: {
253
+ type: "array",
254
+ items: { type: "string" }
255
+ },
256
+ fetchErrorIds: {
257
+ type: "array",
258
+ items: { type: "string" }
259
+ },
260
+ rejectedIds: {
261
+ type: "array",
262
+ items: { type: "string" }
263
+ }
264
+ }
265
+ });
266
+
267
+ fastify.addSchema({
268
+ $id: makeSchemaUri("routeResponseUpdate"),
269
+ type: "object",
270
+ required: [ "matchedCount", "modifiedCount", "upsertedCount" ],
271
+ properties: {
272
+ matchedCount: { type: "integer" },
273
+ modifiedCount: { type: "integer" },
274
+ upsertedCount: { type: "integer" },
275
+ upsertedId: { type: [ "string", "null" ] } // string|null => the field is nullable !
276
+ }
277
+ });
278
+
279
+ fastify.addSchema({
280
+ $id: makeSchemaUri("routeResponseDelete"),
281
+ type: "object",
282
+ required: [ "deletedCount" ],
283
+ properties: {
284
+ deletedCount: { type: "integer", minimum: 0 }
285
+ }
286
+ });
287
+
288
+ fastify.addSchema({
289
+ $id: makeSchemaUri("routeResponseError"),
290
+ type: "object",
291
+ required: [],// [ "message", "info", "method", "url" ],
292
+ properties: {
293
+ message: { type: "string" },
294
+ info: {}, // only using `{}` equates to JS "Any" type
295
+ method: { type: "string" },
296
+ url: { type: "string" },
297
+ requestBody: {}
298
+ }
299
+ });
300
+
301
+ fastify.addSchema({
302
+ $id: makeSchemaUri("routeResponseCount"),
303
+ type: "object",
304
+ required: [ "count" ],
305
+ properties: {
306
+ count: {
307
+ type: "integer",
308
+ minimum: 0
309
+ }
310
+ }
311
+ })
312
+
313
+ return fastify
314
+ }
315
+
316
+
317
+ export default addSchemas;
318
+
package/src/server.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * serve a fastify app
3
+ */
4
+
5
+ import build from "#src/app.js";
6
+ import { PORT, HOST } from "#constants";
7
+
8
+ /**
9
+ * @param {import("#types").ServeModeType} serveMode
10
+ */
11
+ async function server (serveMode) {
12
+ if ([ "dev", "prod" ].includes(serveMode)) {
13
+ serveMode = "default";
14
+ }
15
+
16
+ const fastify = await build(serveMode);
17
+ try {
18
+ fastify.listen({ port: PORT, host: HOST });
19
+ } catch(err) {
20
+ fastify.log.error(err);
21
+ process.exit(1);
22
+ }
23
+ };
24
+
25
+ export default server;
package/src/types.js ADDED
@@ -0,0 +1,97 @@
1
+ /** @typedef {import("node:test")} NodeTestType */
2
+
3
+ /** @typedef {import("fastify").FastifyInstance} FastifyInstanceType */
4
+ /** @typedef {import("fastify").FastifyReply} FastifyReplyType */
5
+ /** @typedef {import("fastify").FastifySchema} FastifySchemaType */
6
+
7
+ /** @typedef {import("mongodb").Db} MongoDbType */
8
+ /** @typedef {import("mongodb").ObjectId } MongoObjectId */
9
+ /** @typedef {import("mongodb").Collection} MongoCollectionType */
10
+ /** @typedef {import("mongodb").InsertManyResult} MongoInsertManyResultType */
11
+ /** @typedef {import("mongodb").InsertOneResult} MongoInsertOneResultType */
12
+ /** @typedef {MongoInsertManyResultType | MongoInsertOneResultType} MongoInsertResultType */
13
+ /** @typedef {import("mongodb").UpdateResult} MongoUpdateResultType */
14
+ /** @typedef {import("mongodb").DeleteResult} MongoDeleteResultType */
15
+ /** @typedef {import("mongodb").MongoClient} MongoClientType */
16
+ /** @typedef {import("mongodb").FindCursor} MongoFindCursorType */
17
+
18
+ /** @typedef {import("ajv").ValidateFunction} AjvValidateFunctionType */
19
+
20
+ /** @typedef {"dev"|"prod"} ServeModeType */
21
+
22
+ /** @typedef {"uri"|"manifestShortId"|"canvasUri"} AnnotationsDeleteKeyType */
23
+
24
+ /** @typedef {2|3} IiifPresentationVersionType */
25
+ /** @typedef {"manifests2"|"manifests3"|"annotations2"|"annotations3"} CollectionNamesType */
26
+ /**
27
+ * @typedef InsertResponseType
28
+ * @type {object}
29
+ * @property {number} insertedCount - the number of documents that were properly inserted
30
+ * @property {Array<string|MongoObjectId>} insertedIds - the ids of the documents that were properly inserted
31
+ * @property {Array<string|MongoObjectId>} [preExistingIds] - the ids of documents that were aldready inserted in a collection with a UNIQUE clause. USED ONLY BY `manifests2` and `manifests3`
32
+ * @property {Array<string>} [fetchErrorIds] - the ids of referenced documents that could not be fetched. USED ONLY BY `manifests2` and `manifests3`
33
+ * @property {{ [x: string]: string }} [rejectedIds] - the ids of the documents that did not pass validation, mapped to validation errors. USED ONLY BY `manifests2` and `manifests3`
34
+ */
35
+
36
+ /**
37
+ * @typedef {Array<InsertResponseType>} InsertResponseArrayType
38
+ */
39
+
40
+ /**
41
+ * @typedef UpdateResponseType
42
+ * @type {object}
43
+ * @property {number} matchedCount
44
+ * @property {number} modifiedCount
45
+ * @property {number} upsertedCount
46
+ * @property {string?} [upsertedId]
47
+ */
48
+
49
+ /**
50
+ * @typedef DeleteResponseType
51
+ * @type {object}
52
+ * @property {number} deletedCount
53
+ */
54
+
55
+ /**
56
+ * @typedef {"read"|"insert"|"update"|"delete"} DataOperationsType
57
+ * the allowed mongo operations.
58
+ */
59
+
60
+ /**
61
+ * @typedef Manifest2InternalType
62
+ * app and database-side structure of IIIF manifests
63
+ * @type {object}
64
+ * @property {string} ["@id"] - the manifest's '@id'
65
+ * @property {string} manifestShortId
66
+ * @property {string[]} canvasIds
67
+ */
68
+
69
+ /**
70
+ * @typedef IiifCollection2Type
71
+ * IIIF manifests collection
72
+ * @type {object}
73
+ * @property {string} ["@context"]
74
+ * @property {string} ["@id"]
75
+ * @property {"sc:Collection"} ["@type"]
76
+ * @property {string?} label
77
+ * @property {Array<{ "@type": "sc:Manifest", "@id": string }>} members
78
+ */
79
+
80
+ /**
81
+ * @typedef {import("#manifests/manifests2.js").Manifests2InstanceType} Manifests2InstanceType
82
+ * type of an instance of the Manifests2 class.
83
+ */
84
+ /**
85
+ * @typedef {import("#annotations/annotations2.js").Annotations2InstanceType} Annotations2InstanceType
86
+ * type of an instance of the Annotations2 class.
87
+ */
88
+ /**
89
+ * @typedef {import("#manifests/manifests3.js").Manifests3InstanceType} Manifests3InstanceType
90
+ * type of an instance of the Manifests3 class.
91
+ */
92
+ /**
93
+ * @typedef {import("#annotations/annotations3.js").Annotations3InstanceType} Annotations3InstanceType
94
+ * type of an instance of the Annotations3 class.
95
+ */
96
+
97
+ export {}