@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,88 @@
1
+ # Architecture
2
+
3
+ This is a high-level overview of the Aiiinotate and a good place to start if you want to work on the app
4
+
5
+ ---
6
+
7
+ ## Top level plugins
8
+
9
+ Fastify uses a [**plugin system**](https://fastify.dev/docs/latest/Guides/Plugins-Guide/). In short, it means that,
10
+ - to access the global `fastify` instance, your code needs to be a plugin
11
+ - all plugins must export a single function that registers the plugin on the global fastify instance
12
+
13
+ This can be quite convoluted and conflict with a "normal" module architecture, so we do a mix of the two:
14
+ - **all modules at the root** of `src/` are plugins (except `config`)
15
+ - **nested plugins** within each of the above are defined when they need to access the fastify instance.
16
+
17
+ ```
18
+ ./src/fixtures/ - module storing and serving data files for test fixtures
19
+ ./src/schemas/ - JsonSchemas definition and validation
20
+ ./src/db/ - connects the app to the mongojs database
21
+ ./src/data/ - routes and modules to read/write data
22
+
23
+ ```
24
+
25
+ ---
26
+
27
+ ## The `data` plugin
28
+
29
+ ### In general
30
+
31
+ As you can see, the `data` plugin stores the vast majority of the app's logic. It defines:
32
+ - **`route.js` files** HTTP routes for user interactions
33
+ - **`collection classes`** (`abstractCollection.js`, `manifests(2|3).js`, `annotations(2|3).js`) for each MongoJS collection that handles all internal functionnalities for all collections (read/write/update/delete data)
34
+
35
+ Each `route` and `class` is a fastify plugin. Since the `data` plugin is registered last, it can access functionalities from all other root plugins defined above.
36
+
37
+ ### Structure and inheritence
38
+
39
+ ```
40
+ ├── index.js // `data` plugin root
41
+ ├── routes.js // generic routes
42
+ ├── annotations
43
+ │   ├── annotations2.js // plugin for IIIF presentation 2 annotations
44
+ │   ├── annotations3.js // plugin for IIIF presentation 3 annotations
45
+ │   ├── routes.js // routes for both plugins
46
+ ├── collectionAbstract.js // abstract class with functionnalities for all plugins
47
+ ├── manifests
48
+    ├── manifests2.js // plugin for IIIF presentation 2 manifests
49
+    ├── manifests3.js // plugin for IIIF presentation 3 manifests
50
+    ├── routes.js // routes for both plugins
51
+ ```
52
+
53
+ At a high level, `route` files receive data and relegate internal mongoJS interaction to the `collection classes`. Once these processes are finished, the `collection classes` return data to the `routes`, which send the response to the users. So:
54
+
55
+ - the work of `routes` is to define input/output JsonSchemas and delegate to the proper `collection classes`
56
+ - the work of `classes` is to do the database interaction, data formatting etc.
57
+
58
+ Our 5 `collection classes` (`collectionAbstract`, `annotations2`, `annotations3`, `manifests2`, `manifests3`) use class inheritence: all classes inherit from `collectionAbstract`
59
+ - `collectionAbstract` defines collection-agnostic processes that can be used by all other classes.
60
+ - other classes implement functionnalities for a specific data type and collection.
61
+
62
+ ---
63
+
64
+ ## Details: why plugins are complicated ?
65
+
66
+ If using a plugin-only architecture, **you should not do "local" imports** (imports from one part of your app to another): everything that you want to communicate between apps must be registered as a plugin on the global fastify instance, and then other files need to access the fastify instance:
67
+
68
+ ```js
69
+ // here, we define a root plugin with 2 subplugins (1st is a decorator, 2nd a normal plugin).
70
+ fastify.register((instance, opts, done) => {
71
+
72
+ // decorator
73
+ instance.decorate('util', (a, b) => a + b)
74
+ console.log(instance.util('that is ', 'awesome'))
75
+
76
+ // plugin
77
+ fastify.register((instance, opts, done) => {
78
+ console.log(instance.util('that is ', 'awesome'))
79
+ done()
80
+ })
81
+
82
+ done()
83
+ })
84
+ ```
85
+
86
+ In the above example, functionnalities defined in each plugin (except decorators) are encapsulated: a plugin is a scope and everything that's defined in a plugin must be accessed through the fastify instance. There is also the question of plugins vs. decorators, plugin definition order...
87
+
88
+ In short, if using a plugin-only architecture, every single one of your files would have to be a plugin, registered in the proper order... It would be hell.
@@ -0,0 +1,58 @@
1
+ # DATABASE AND MIGRATIONS
2
+
3
+ ---
4
+
5
+ ## Collections
6
+
7
+ - `annotations2`: a collection of annotations following the IIIF Presentation API 2.x (OpenAnnotation specification)
8
+ - `annotations3`: a collection of annotations following the IIIF Presentation API 3.x (WebAnnotation specification)
9
+ - `manifests2`: an index of manifests (manifest ID and array of canvas IDs of the manifest) following the IIIF Presentation API 2.x (OpenAnnotation specification)
10
+ - `manifests3`: an index of manifests (manifest ID and array of canvas IDs of the manifest) following the IIIF Presentation API 3.x (WebAnnotation specification)
11
+
12
+ Conversion between IIIF Presentation APIs 2 and 3 is not implemented.
13
+
14
+ ---
15
+
16
+ ## Validation rules
17
+
18
+ Two levels of validation rules are implemented:
19
+ - route-level validation
20
+ - database-level validation
21
+
22
+ At route-level, we ensure that the data has the absolutely necessary data to work with annotations.
23
+
24
+ At database-level (see [`src/schema`](https://github.com/Aikon-platform/aiiinotate/blob/main/src/schemas/) module, we define a complete model (in JSONSchema) to describe annotations.
25
+
26
+ **TLDR**: when inserting annotations (i.e., from an AnnotationList)
27
+ 1. at [route-level](https://github.com/Aikon-platform/aiiinotate/blob/main/src/data/annotations/routes.js), we ensure that the AnnotationList is generally valid (contains a `resource` with annotations, each annotations contains a `on`...)
28
+ 2. in the [`Annotations2`](https://github.com/Aikon-platform/aiiinotate/blob/main/src/data/annotations/annotations2.js) module, we do some extra checks and extra minimal reformatting to ensure that our annotations all have the same structure (i.e., convert `annotation.on` values to an array of `SpecificResources`). After finishing the cleanup, we are certain that an annotation can be inserted
29
+ 3. when inserting an annotation in the MongDB `annotations2`, MongoDB uses the `annotation` schema defined in [`src/schemas/schemasPresentation2`](https://github.com/Aikon-platform/aiiinotate/blob/main/src/schemas/schemasPresentation2.js) to validate annotations before inserting.
30
+
31
+ ---
32
+
33
+ ## Migrations
34
+
35
+ Migrations are used to specify changes to the database's structure (creation and deletion of collections and indexes, changes to collection options such as validation rules etc.). Migration is done using [`migrate-mongo`](https://github.com/seppevs/migrate-mongo), a command-line interface for npm.
36
+
37
+ *Note that here, migrations only concern changes to the structure, not changes to the data.*
38
+
39
+ Where things get a bit complicated is that, while in use, our app uses a single database, in **dev mode, our app uses 2 databses**:
40
+ - a `main` database (default database that will be put in production)
41
+ - a `test` database (empty database to run tests, add dummy data to and so on).
42
+
43
+ For consistency, `test` must mirror the structure of `main`: same collections, indexes, validation rules etc. So, we must manage our migrations so that **all changes to the structure of `main` are reflected in the structure of `test`**. This is not possible natively through `migrate-mongo`. Managing both databases in parrallel manually would risk a lot of inconsistencies.
44
+
45
+
46
+ Our solution is to automate the management of both databases in parrallel:
47
+ - there are **2 migration config files**, one for each database
48
+ - both migration config files point to **the same migration scripts folder**, so that both database can apply the same migrations
49
+ - execute all migrations by **wrapping `migrate-mongo` in a homemade script**: [`scripts/migrations.sh`](../scripts/migrations.sh).
50
+
51
+ ```
52
+ root/
53
+ |__migrations/
54
+ |__migrationScripts // folder containing all migration scripts. consumed by both migration config files.
55
+ |__baseConfig.js // base config file that both config files are derived from
56
+ |__migrate-mongo-config-main.js // config file for the main db
57
+ |__migrate-mongo-config-test.js // config file for the test db
58
+ ```
@@ -0,0 +1,43 @@
1
+ # Development notes: IIIF compatibility and variations between IIIF prsentation APIxs 3.x and IIIF 2.x
2
+
3
+ The annotation server uses an internal data model that can convert to and from annotation models defined in IIIF 2.x and 3.x presentation API. Respectively:
4
+ - IIIF 3.x follows the [W3C Web Annotations standard (WA)](https://www.w3.org/TR/annotation-model/)
5
+ - IIIF 2.x follows the [Open Annotations standard (OA)] (http://www.commonsemantics.com/oa/Open%20Annotation%20Data%20Model%20Primer.html#examples)
6
+
7
+ In general, there are [breaking changes](https://iiif.io/api/presentation/3.0/change-log/#1-breaking-changes) between IIIF 2.x and 3.x. For annotations, the OA model allows things that are not possible in WA, and vice versa. Our internal data model tries to store a maximum amount of data from WA and OA annotations, and then do the necessary conversion to produce valid OA/WA annotations.
8
+
9
+ To make things funnier, the original Open Annotations standard website is no longer available.
10
+
11
+ ## Annotations and the `motivation` attribute
12
+
13
+ ### The problem
14
+
15
+ Both OA and WA have a `motivation` attribute that describes the function of the annotation in relation to the Manifest. Mainly, the use of `painting` (WA) / `sc:painting` (OA) indicates that the annotation is the primary content of the canvas and should be rendered.
16
+ - WA allows 2 values: `painting`, `supplementing`
17
+ - OA allows a more values and defines an ontology. [IIIF 2.1 specifies](https://iiif.io/api/presentation/2.1/#comment-annotations) that the value given to a non-painting annotation should be `oa:commenting`
18
+
19
+ In practice, in SAS/Aikon, non-painting annotations have the `motivation`: `[ "oa:tagging", "oa:supplementing" ]`.
20
+
21
+ ### What we do
22
+
23
+ In an annotation, the `motivation` field is a `string[]` array that accepts all values it receives.
24
+ - **when writing data from IIIF to DB**, `motivation` values are connected to string arrays but are not verified.
25
+ - **when converting data from DB to IIIF**, `motivation` values stored in the database are processed:
26
+ - converting to IIIF 2.x:
27
+ - if WA values are stored in the DB, they are converted to 2.x: `painting => sc:painting`, `supplementing => oa:commenting`.
28
+ - if OA values are stored in the DB, they are kept as is
29
+ - converting to IIIF 3.x:
30
+ - if WA values are in the DB, they are kept as is
31
+ - if OA values are in the DB, they are converted to `painting` (if `sc:painting` is in the array of motivations stored) or `commenting` otherwise.
32
+
33
+ ## DCTypes and the `body.type` attribute
34
+
35
+ ### Problem
36
+
37
+ - WA allows : `Dataset`, `Image`, `Video`, `Sound`, `Text`
38
+ - OA allows : `text`, `image`, `sound`, `dataset`, `software`, `interactive`, `event`, `physical`, `object`
39
+ - In IIIF 2.x, values are prefixed by `dctypes:`: `dctypes:text`.
40
+
41
+ ### Solution
42
+
43
+ Since there is a close mapping between the two, we convert to WA allowed types before saving to database.
@@ -0,0 +1,143 @@
1
+ # Notes, quirks and troubleshooting
2
+
3
+ ---
4
+
5
+ ## Notes
6
+
7
+ ### Uniqueness
8
+
9
+ As of writing (09.10.25), there are no uniqueness constraints on annotations. There is only a uniqueness constraint on collection `manifest2` on field `manifest2.@id` (the ID of a manifest).
10
+
11
+ Ideally, we would want to avoid having duplicate annotations in the database. This is more complicated in practice: at least for `annotions2`, an annotation's `@id` field is re-generated and a random part (an UUID) is generated at insert time. This means that, when trying to store the same annotation (with the same `@id`), the `@id` is changed, and so a different value is inserted.
12
+
13
+ This means that we can't have a uniqueness constraint on `@id` or `id` fields of annotations. Another option would be to have a uniqueness constraint on annotation targets (no 2 annotations can annotate the same region), but this behaviour seems brittle in practice, so it's not yet implemented.
14
+
15
+ ### Concurrency
16
+
17
+ For clients, concurrency/parrallelization (i.e., with JS `Promise.all()`) on insert/update should be avoided because it can cause a data race: several processes can attempt to write the same thing in parrallel.
18
+
19
+ For example, when inserting annotations, the manifests related to each annotation are inserted in parrallel. Since this is a side effect, 2 processes may unknowingly try to insert the same manifest in the database, which causes a uniqueness constraint to fail. This error can be hard to debug, so it's best to avoid concurrency at write time.
20
+
21
+ ---
22
+
23
+ ## Dev quirks
24
+
25
+ Sometimes, node/fastify can be weird. When scratching your head at dev errors, look here:)
26
+
27
+ ### Error swallowing at app build
28
+
29
+ Errors that happen when a plugin is being registered will cause the app's startup to fail, without necessarily throwing an error.
30
+
31
+ This is especially true for test cases that build the fastify app:
32
+
33
+ ```
34
+ test
35
+ => build fastify app
36
+ => build step fails silently
37
+ => test fails
38
+ ```
39
+
40
+ #### Troubleshooting
41
+
42
+ - normal behaviour: when a runtime error happens, the failing test will log the error on the console
43
+ - what this error looks like:
44
+ - tests fail **very quickly**,
45
+ - without throwing an error, seemingly without even running the test suite
46
+ - the proces doesn't exit, although all tests have failed
47
+ - when `npm run test` fails like this, run `npm run start`. See if normal startup throws an error
48
+ - NOTE: normal startup not throwing does NOT mean that the build step necessarily worked
49
+
50
+ #### Possible help/solutions
51
+
52
+ - find a way to stisfyingly use `try...catch` at plugin registration.
53
+ - look into these issues: [2694](https://github.com/fastify/fastify/issues/2694)
54
+ - in particular, it may be an issue specific to async plugins ?
55
+
56
+ ### Route response schema definition
57
+
58
+ For some reason, route schema definition is much less flexible for responses than for queries.
59
+
60
+ #### The problem
61
+
62
+ **Query schemas**: In queries (`schema.params` or `schema.querystring`), fastify is very permissive. You can use unresolved schemas (with `$ref`), save entire schemas as JsonSchemas ...
63
+
64
+ **Response schemas**: Response schemas have more constraints.
65
+ - **response schemas are defined at route level** and cannot be stored as full JsonSchemas:
66
+ ```js
67
+ // this will be an invalid response schema: it is trying to store a complete response schema as a JsonSchema
68
+ fastify.addSchema({
69
+ $id: makeSchemaUri("routeResponsePost"),
70
+ properties: {
71
+ 200: { ... },
72
+ 500: { ... },
73
+ }
74
+ })
75
+
76
+ // this is a fixed version: an object containing schemas, not a full JsonSchema, to be used inside a Route definition
77
+ schema: {
78
+ response: {
79
+ 200: { ... },
80
+ 500: { ... }
81
+ }
82
+ }
83
+ // if you want to reuse a schema, store it as a JS object and import it.
84
+ ```
85
+ - **unresolved response schemas (`$ref`) are forbidden**. You cannot use `$ref`. In the app, use `fastify.schemasResolver` to resolve the schema to a plain `JsonSchema` without `$ref`.
86
+
87
+ #### The fix
88
+
89
+ In short, here's **how to use shared schemas in responses**:
90
+ 1. Define payload schemas for different response cases:
91
+ ```js
92
+ // in case of a POST success
93
+ fastify.addSchema({
94
+ $id: makeSchemaUri("routeResponseInsert"),
95
+ type: "object",
96
+ // ...properties
97
+ });
98
+
99
+ // in case of a POST error
100
+ fastify.addSchema({
101
+ $id: makeSchemaUri("routeResponseError"),
102
+ type: "object",
103
+ // ...properties
104
+ });
105
+ ```
106
+ 2. Resolve schemas and use in responses
107
+ ```js
108
+ const routeResponseInsert = fastify.getSchema("...");
109
+ const routeResponseError = fastify.getSchema("...");
110
+ fastify.post(
111
+ "/annotations/:iiifPresentationVersion/create",
112
+ {
113
+ schema: {
114
+ body: routeAnnotations2Or3Schema,
115
+ response: {
116
+ 200: routeResponseInsert,
117
+ 500: routeResponseError
118
+ }
119
+ }
120
+ },
121
+ async (req, rep) => {}
122
+ )
123
+ ```
124
+
125
+ ### `migrate revert-all && migrate apply`
126
+
127
+ #### The problem
128
+
129
+ Sometimes, you want to remove all migrations and then rerun them. This will cause an error like:
130
+
131
+ ```bash
132
+ {
133
+ ...,
134
+ errmsg: 'namespace aiiinotate.manifests2 already exists, but with different options: { uuid: UUID("65ca87c4-ddce-4c2d-a6f9-08ff6b70c0a7"), validationLevel: "strict", validationAction: "error" }',
135
+ code: 48,
136
+ codeName: 'NamespaceExists',
137
+ ...
138
+ }
139
+ ```
140
+
141
+ #### The fix
142
+
143
+ I figure it's an error due to cache or something like that. Either wait or manually delete the collection or the entire database using `mongosh`.
package/docs/docker.md ADDED
@@ -0,0 +1,98 @@
1
+ # Docker usage
2
+
3
+ The `docker/` folder contains:
4
+ - a `Dockerfile` for aiiinotate
5
+ - a `docker-comose` to integrate aiiinotate with MongoDB, with volume persistance
6
+ - a script to import data into a dockerized aiiinotate instance
7
+
8
+ ---
9
+
10
+ ## Docker setup
11
+
12
+ 1. [Install docker](https://docs.docker.com/engine/install/)
13
+ 2. On linux, add your user to the `docker` group (see [full docs](https://docs.docker.com/engine/install/linux-postinstall/))
14
+
15
+ ```bash
16
+ sudo groupadd docker
17
+ sudo usermod -aG docker $USER
18
+ newgrp docker
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Usage
24
+
25
+ Note that all `docker` commands must be run from the `docker/` directory!
26
+
27
+ 1. **Clone the repo** and `cd` in it
28
+
29
+ ```bash
30
+ git clone git@github.com:Aikon-platform/aiiinotate.git
31
+ cd aiiinotate/
32
+ ```
33
+
34
+ 2. **Create a `.env` file** at `docker/.env` (from the root of the project):
35
+ ```bash
36
+ cp ./config/.env.template ./docker/.env
37
+ vim ./docker/.env # do your edits
38
+ ```
39
+
40
+ 3. **Edit the `.env` file**. Pay attention to the following values:
41
+ - `MONGODB_HOST=mongo` (container name)
42
+ - `AIIINOTATE_HOST=aiiinotate` (container name)
43
+ - `AIIINOTATE_SCHEME=http`
44
+ - `AIIINOTATE_LOG_TARGET=stdout` (Docker aldready collects stdout logs, so setting log target to `file` is redundant)
45
+ - set `AIIINOTATE_PUBLIC_URL` to the URL at which your aiiinotate instance will be accessible (e.g., `aiiinotate.enpc.fr`).
46
+
47
+ 4. **Build the containers**
48
+
49
+ ```bash
50
+ cd docker
51
+ bash docker.sh build
52
+ ```
53
+
54
+ 5. **Start the containers**
55
+
56
+ ```bash
57
+ bash docker.sh start
58
+ ```
59
+
60
+ 6. (Optional) **import data** into the running aiiinotate instance. `path/to/dir` is a path to a folder containing AnnotationLists or manifests to import.
61
+
62
+ ```bash
63
+ bash docker_aiiinotate_import.sh <manifests|annotations> path/to/dir
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Troubleshooting and useful commands
69
+
70
+ View logs:
71
+
72
+ ```bash
73
+ docker logs docker-aiiinotate-1 -f
74
+ ```
75
+
76
+ Access `mongosh` within the Mongo container:
77
+
78
+ ```bash
79
+ docker exec -it docker-mongo-1 mongosh
80
+ ```
81
+
82
+ Check running ports in the Web container:
83
+
84
+ ```bash
85
+ docker exec -it docker-web-1 ss -tnl
86
+ ```
87
+
88
+ View globally installed NPM packages in the Web container:
89
+
90
+ ```bash
91
+ docker exec -it docker-web-1 npm list -g --depth=0
92
+ ```
93
+
94
+ CURL the Web container
95
+
96
+ ```bash
97
+ docker exec -it docker-web-1 curl http://0.0.0.0:4444 # change 4444 by your $AIIINOTATE_PORT
98
+ ```
@@ -0,0 +1,34 @@
1
+ # Scalability
2
+
3
+ ## Results
4
+
5
+ In [benchmarks](https://github.com/paulhectork/aiiinotate-benchmark), aiiinotate response times are between 1/100th and 1/10th of a second with up to 10,000,000 annotations.
6
+
7
+ ![benchmark results](./includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png)
8
+
9
+ ## Discussion
10
+
11
+ ### Write times
12
+
13
+ Write times (`Write anno. list`, `Write anno.`) presented above are in all probability lower than in real world applications.
14
+
15
+ Indeed, a big bottleneck when inserting annotations is to fetch an annotation's target manifest through HTTP, in ordrer to fill the `canvasIdx` field.
16
+
17
+ When running the benchmark, all annotation URIs are on `https://localhost`, an URI on localhost that is inaccessible. The request thus fails instantly.
18
+
19
+ The reason this was done is that HTTP requests are non-deterministic: if we make requests to an external server storing IIIF manifests, then we end up also benchmarking this server. By fetching manifests on an inaccessible `https://localhost`, this non-deterministic process becomes deterministic and the benchmark makes more sense.
20
+
21
+ ### >10M annotations
22
+
23
+ After benchmarking for 10M annotations, we delete the database and attempt to insert 100M annotations to benchmark response times.
24
+
25
+ With 100M annotations stored, the database size becomes an issue. These scalability limits are mostly caused by MongoDB, and not by the aiiinotate app itself: the larger the database, the more hard drive memory it uses, and the more RAM it uses (since indexes are stored in the RAM). To store >100M annotations, we should scale hardware:
26
+
27
+ - **vertical scaling**: more RAM and disk space
28
+ - **horizontal scaling**: use a Mongo [sharded cluster](https://www.mongodb.com/docs/manual/sharding/).
29
+
30
+ It should be noted that the benchmark stress tests your machine, aiiinotate and Mongo at the same time:
31
+ - results presented above are obtained by running the benchmark, aiiinotate and Mongo on a single machine. All 3 are thus competing for RAM access and CPU.
32
+ - it uses mutliple threads, costly JSON-stringification, fast I/O and has a high throughput: it runs a lot of queries to aiiinotate, and to the MongoDB.
33
+
34
+ In real-world examples, with less throughput and when not running the benchmark itself, there should be less stress on your MongoDB server, thus less CPU usage, thus possibly more scalability.