aiiinotate 0.10.0 → 0.10.1
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.
- package/README.md +1 -1
- package/docs/cli.md +7 -4
- package/package.json +1 -1
- package/src/data/annotations/annotations2.js +0 -1
- package/src/data/manifests/manifests2.js +6 -2
- package/src/utils/utils.js +47 -21
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ npm run cli -- import <arguments>
|
|
|
180
180
|
|
|
181
181
|
## Test coverage
|
|
182
182
|
|
|
183
|
-
aiiinotate is well tested:
|
|
183
|
+
aiiinotate is well tested: **~90% test coverage** on all files !
|
|
184
184
|
|
|
185
185
|
```
|
|
186
186
|
ℹ ----------------------------------------------------------------------------------------
|
package/docs/cli.md
CHANGED
|
@@ -64,17 +64,20 @@ aiiinotate migrate revert-all
|
|
|
64
64
|
|
|
65
65
|
## `import`: import data
|
|
66
66
|
|
|
67
|
+
The CLI can be used to import IIIF annotations or to index IIIF manifests in aiiinotate.
|
|
68
|
+
|
|
67
69
|
```bash
|
|
68
70
|
# in prod
|
|
69
|
-
aiiinotate import -i 2 -f <path/to/import/file.txt>
|
|
71
|
+
aiiinotate import <manifests|annotations> -i 2 -f <path/to/import/file.txt>
|
|
70
72
|
```
|
|
71
73
|
|
|
72
74
|
Where:
|
|
75
|
+
- `<manifests|annotations>` is the kind of data to import: manifests or annotations
|
|
73
76
|
- `-i` `--iiif-version` (`2|3`) is the IIIF Presentation API version of the data to import:
|
|
74
|
-
- if `-i 3`, we import IIIF presentation 3.x
|
|
75
|
-
- if `-i 2`, we import IIIF presentation 2.x
|
|
77
|
+
- if `-i 3`, we import IIIF presentation 3.x data
|
|
78
|
+
- if `-i 2`, we import IIIF presentation 2.x data
|
|
76
79
|
- `-f --file` is a relative or absolute path the the import file. This import file:
|
|
77
|
-
- contains a list of paths to annotation lists
|
|
80
|
+
- contains a list of paths to annotation lists, annotation pages or manifests
|
|
78
81
|
- with 1 path per line
|
|
79
82
|
- paths can be relative or absolute
|
|
80
83
|
|
package/package.json
CHANGED
|
@@ -345,7 +345,6 @@ class Annotations2 extends CollectionAbstract {
|
|
|
345
345
|
const totalCount = await this.#memoizePaginationTotalCount(queryFilter);
|
|
346
346
|
|
|
347
347
|
const skip = Math.max((page-1) * pageSize, 0); // number of queried items up until the previous page included.
|
|
348
|
-
|
|
349
348
|
const cursor = await this.find(queryFilter, {}, true);
|
|
350
349
|
const annotations = await cursor
|
|
351
350
|
.sort({ "@id": 1 })
|
|
@@ -3,7 +3,7 @@ import fastifyPlugin from "fastify-plugin";
|
|
|
3
3
|
import CollectionAbstract from "#data/collectionAbstract.js";
|
|
4
4
|
import { getManifestShortId } from "#utils/iiif2Utils.js";
|
|
5
5
|
import { formatInsertResponse } from "#utils/routeUtils.js";
|
|
6
|
-
import { inspectObj, visibleLog, ajvCompile, memoize } from "#utils/utils.js";
|
|
6
|
+
import { inspectObj, visibleLog, ajvCompile, memoize, objectHasKey } from "#utils/utils.js";
|
|
7
7
|
import { IIIF_PRESENTATION_2_CONTEXT } from "#utils/iiifUtils.js";
|
|
8
8
|
import { PUBLIC_URL } from "#constants";
|
|
9
9
|
|
|
@@ -70,7 +70,11 @@ class Manifests2 extends CollectionAbstract {
|
|
|
70
70
|
*/
|
|
71
71
|
#validateManifest(manifest) {
|
|
72
72
|
if (!this.validatorManifest(manifest)) {
|
|
73
|
-
|
|
73
|
+
let info = {};
|
|
74
|
+
if (objectHasKey(manifest, "@id")) {
|
|
75
|
+
info = { "@id": manifest["@id"] };
|
|
76
|
+
}
|
|
77
|
+
throw this.insertError("validateManifest: invalid manifest structure", info);
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
|
package/src/utils/utils.js
CHANGED
|
@@ -282,41 +282,67 @@ const recursiveSort = (x) => {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
|
-
* Map cache with a timeout
|
|
286
|
-
*
|
|
285
|
+
* Map cache with a timeout and a max size:
|
|
286
|
+
* - after `timeout` ms have passed, clears the cache of the key `n`.
|
|
287
|
+
* - to avoid the cache to grow unbounded, we define a max size after which
|
|
288
|
+
* we delete the greatest items in the cache.
|
|
287
289
|
*
|
|
288
290
|
* NOTE: LIMITATIONS:
|
|
289
|
-
* - `memoize` converts `fn` to an async function to work with both
|
|
290
|
-
*
|
|
291
|
-
* - `
|
|
291
|
+
* - `memoize` converts `fn` to an async function to work with both
|
|
292
|
+
* sync/async patterns, remember to await !
|
|
293
|
+
* - the args applied to `fn` must be JSON-stringify-able: a primitive,
|
|
294
|
+
* an array, or an object, but not a function, a class or class instance
|
|
292
295
|
*
|
|
293
296
|
* adapted from: https://dev.to/codewithjohnson/the-power-of-a-simple-cache-system-with-javascript-map-3j01
|
|
294
297
|
*
|
|
295
298
|
* @param {Function} fn - the function whose result will be cached
|
|
296
299
|
* @param {number} timeout - timeout in ms to clear the cache of a newly assigned value
|
|
300
|
+
* @param {number} maxSize - maximm number of elements in the cache
|
|
297
301
|
* @returns {async Function} - a function that takes a value and caches its result.
|
|
298
302
|
*/
|
|
299
|
-
const memoize = (fn, timeout = 2000) => {
|
|
303
|
+
const memoize = (fn, timeout = 2000, maxSize = 200) => {
|
|
304
|
+
if (timeout <= 0 || maxSize <= 0) {
|
|
305
|
+
throw new Error("memoize: 'timeout' and 'maxSize' must be greater than 0.")
|
|
306
|
+
}
|
|
300
307
|
const cache = new Map();
|
|
301
|
-
return async (
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
308
|
+
return async (...args) => {
|
|
309
|
+
// if cache.size > cacheSize, remove the oldest items.
|
|
310
|
+
const extraCount = cache.size - maxSize;
|
|
311
|
+
if (extraCount > 0) {
|
|
312
|
+
// get the `extraCount` oldest items in the cache.
|
|
313
|
+
const deleteItems = [ ...cache.entries() ] // convert iterator to array
|
|
314
|
+
.map(([ k, { timestamp }]) => [ k, timestamp ])
|
|
315
|
+
.sort((a, b) => a[1] - b[1])
|
|
316
|
+
.slice(0, extraCount);
|
|
317
|
+
|
|
318
|
+
deleteItems.forEach(([ k, _ ]) => cache.delete(k));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// stringify `args` to `key`, the key to find in the cache.
|
|
322
|
+
// since `map` is a key-value store, we need to stringify `args`
|
|
323
|
+
// to ensure it can be used as a key to ensure consistency,
|
|
324
|
+
// we sort our objects/array before stringifying
|
|
325
|
+
const key = JSON.stringify(recursiveSort(args));
|
|
326
|
+
|
|
327
|
+
// fetch result promise, from cache or by executing `fn`.
|
|
328
|
+
// instead of caching the result, we cache the promise:
|
|
329
|
+
// all concurrent callers awaiting the same key get the same promise
|
|
330
|
+
// instance, so `fn` is only ever called once per unique key,
|
|
331
|
+
// regardless of how many callers arrive before it resolves.
|
|
332
|
+
let promise;
|
|
306
333
|
if (cache.has(key)) {
|
|
307
|
-
|
|
334
|
+
promise = cache.get(key).promise;
|
|
308
335
|
} else {
|
|
309
|
-
|
|
310
|
-
cache.set(key,
|
|
336
|
+
promise = fn(...args);
|
|
337
|
+
cache.set(key, { timestamp: Date.now(), promise: promise });
|
|
311
338
|
// after `timeout` ms, clear the cache of `key`
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
317
|
-
}
|
|
318
|
-
return result;
|
|
339
|
+
setTimeout(
|
|
340
|
+
() => cache.delete(key),
|
|
341
|
+
timeout
|
|
342
|
+
)
|
|
319
343
|
}
|
|
344
|
+
|
|
345
|
+
return await promise;
|
|
320
346
|
}
|
|
321
347
|
}
|
|
322
348
|
|