@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,410 @@
1
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
2
+
3
+ // TODO: schemas are maybe wayyy too strict.
4
+ // convert all enums (the arrays below) to { type: string } ?
5
+
6
+ const oaSelectorValues = [
7
+ "oa:FragmentSelector",
8
+ "oa:CssSelector",
9
+ "oa:XPathSelector",
10
+ "oa:TextQuoteSelector",
11
+ "oa:TextPositionSelector",
12
+ "oa:DataPositionSelector",
13
+ "oa:SvgSelector",
14
+ "oa:RangeSelector",
15
+ "cnt:ContentAsText", // this one is only described in IIIF Presentation API.
16
+ // also allow values without extension
17
+ "FragmentSelector",
18
+ "CssSelector",
19
+ "XPathSelector",
20
+ "TextQuoteSelector",
21
+ "TextPositionSelector",
22
+ "DataPositionSelector",
23
+ "SvgSelector",
24
+ "RangeSelector",
25
+ "ContentAsText"
26
+ ]
27
+
28
+ const motivationValues = [
29
+ "sc:painting",
30
+ "oa:commenting",
31
+ "oa:describing",
32
+ "oa:tagging",
33
+ "oa:linking",
34
+ "painting",
35
+ "commenting",
36
+ "describing",
37
+ "tagging",
38
+ "linking",
39
+ ]
40
+
41
+ const embeddedBodyTypeValues = [
42
+ "oa:TextualBody",
43
+ "cnt:ContentAsText",
44
+ "dctypes:Text",
45
+ "oa:Tag",
46
+ "TextualBody",
47
+ "ContentAsText",
48
+ "Text",
49
+ "Tag",
50
+ ]
51
+
52
+ function addSchemas(fastify, makeSchemaUri) {
53
+
54
+ /////////////////////////////////////////////
55
+ // SPECIFIC RESOURCES
56
+
57
+ // derived from: https://iiif.io/api/annex/openannotation/context.json
58
+ fastify.addSchema({
59
+ $id: makeSchemaUri("iiifImageApiSelector"),
60
+ type: "object",
61
+ required: [ "@type", "@context" ],
62
+ properties: {
63
+ "@id": { type: "string" },
64
+ "@type": {
65
+ type: "string",
66
+ enum: [ "iiif:ImageApiSelector" ]
67
+ },
68
+ "@context": { type: "string" },
69
+ region: { type: "string" },
70
+ size: { type: "string" },
71
+ rotation: { type: "string" },
72
+ format: { type: "string" },
73
+ quality: { type: "string" },
74
+ }
75
+ });
76
+
77
+ // NOTE: we don't support refinedBy and any other recursive selectors.
78
+ // https://github.com/Aikon-platform/aiiinotate/blob/main/docs/specifications/0_w3c_web_annotations.md#selectors-data-model
79
+ fastify.addSchema({
80
+ $id: makeSchemaUri("oaSelector"),
81
+ type: "object",
82
+ required: [ "@type" ], // could also add `value` or `chars` but one or the other may be used, not both.
83
+ properties: {
84
+ "@id": { type: "string" },
85
+ "@type": {
86
+ oneOf: [
87
+ {
88
+ type: "string",
89
+ enum: oaSelectorValues
90
+ },
91
+ {
92
+ // IIIF 2.1 has examples with multiple `@types`: // `chars` is used by SvgSelector in: https://iiif.io/api/presentation/2.1/#non-rectangular-segments
93
+ type: "array",
94
+ items: {
95
+ type: "string",
96
+ enum: oaSelectorValues
97
+ }
98
+ }
99
+ ]
100
+ },
101
+ value: { type: "string" },
102
+ chars: { type: "string" } // `chars` is used by SvgSelector in: https://iiif.io/api/presentation/2.1/#non-rectangular-segments
103
+ }
104
+ })
105
+
106
+ fastify.addSchema({
107
+ $id: makeSchemaUri("oaOrIiifSelector"),
108
+ type: "object",
109
+ oneOf: [
110
+ { $ref: makeSchemaUri("oaSelector") },
111
+ { $ref: makeSchemaUri("iiifImageApiSelector") },
112
+ ]
113
+ })
114
+
115
+ fastify.addSchema({
116
+ $id: makeSchemaUri("oaChoiceSelector"),
117
+ type: "object",
118
+ required: [ "@type", "default" ],
119
+ properties: {
120
+ "@type": { type: "string", enum: [ "oa:Choice" ] },
121
+ default: { $ref: makeSchemaUri("oaOrIiifSelector") },
122
+ item: { $ref: makeSchemaUri("oaOrIiifSelector") }
123
+ }
124
+ })
125
+
126
+ // selector is either a string, string[], iiifImageApiSelector, iiifImageApiSelector[]. oaSelector, oaSelector[]
127
+ fastify.addSchema({
128
+ $id: makeSchemaUri("selector"),
129
+ anyOf: [
130
+ { type: "string" },
131
+ { type: "array", items: { type: "string" } },
132
+ { $ref: makeSchemaUri("oaOrIiifSelector") },
133
+ { $ref: makeSchemaUri("oaChoiceSelector") },
134
+ {
135
+ type: "array",
136
+ items: { $ref: makeSchemaUri("oaOrIiifSelector") }
137
+ },
138
+ ]
139
+ })
140
+
141
+ fastify.addSchema({
142
+ $id: makeSchemaUri("specificResource"),
143
+ type: "object",
144
+ required: [ "@type", "full", "selector" ],
145
+ properties: {
146
+ "@id": { type: "string" },
147
+ "@type": {
148
+ type: "string",
149
+ enum: [ "oa:SpecificResource" ]
150
+ },
151
+ // NOTE: OA defines a `source` field for SpecificResources, but in IIIF `full` seems to have the same role
152
+ // https://github.com/Aikon-platform/aiiinotate/blob/main/docs/specifications/0_w3c_web_annotations.md#specific-resources-data-model
153
+ full: {
154
+ anyOf: [
155
+ // URI
156
+ { type: "string" },
157
+ // object describing an image
158
+ {
159
+ type: "object",
160
+ required: [ "@id", "@type" ],
161
+ properties: {
162
+ "@id": { type: "string" },
163
+ "@type": { type: "string" }
164
+ }
165
+ }
166
+ ],
167
+ },
168
+ manifestShortId: { type: "string" },
169
+ canvasIdx: {
170
+ anyOf: [
171
+ { type: "number" },
172
+ { type: "null" }
173
+ ]
174
+ },
175
+ manifestUri: {
176
+ anyOf: [
177
+ { type: "string" },
178
+ { type: "null" }
179
+ ]
180
+ },
181
+ xywh: {
182
+ oneOf: [
183
+ {
184
+ type: "array",
185
+ items: { type: "number" }
186
+ },
187
+ { type: "null" }
188
+ ]
189
+ },
190
+ selector: { $ref: makeSchemaUri("selector") },
191
+ purpose: { type: "string" }
192
+ }
193
+ })
194
+
195
+ /////////////////////////////////////////////
196
+ // ANNOTATIONS
197
+ // NOTE : annotations can define both painting and non-painting annotations.
198
+
199
+ fastify.addSchema({
200
+ $id: makeSchemaUri("annotationTarget"),
201
+ type: "array",
202
+ items: {
203
+ anyOf: [
204
+ // URI
205
+ { type: "string" },
206
+ // SpecificResource
207
+ { $ref: makeSchemaUri("specificResource") }
208
+ ]
209
+ }
210
+ })
211
+
212
+ fastify.addSchema({
213
+ $id: makeSchemaUri("motivation"),
214
+ anyOf: [
215
+ { type: "string", enum: motivationValues },
216
+ {
217
+ type: "array",
218
+ items : { type: "string", enum: motivationValues }
219
+ },
220
+ ],
221
+ })
222
+
223
+ fastify.addSchema({
224
+ $id: makeSchemaUri("referencedBody"),
225
+ type: "object",
226
+ required: [ "@id", "@type" ],
227
+ properties: {
228
+ "@id": { type: "string" },
229
+ "@type": { type: "string" }, // should match `dctypes:[a-zA-Z]+`. regex is disabled for performance reasons.
230
+ "format": { type: "string" } // should be a MimeType.
231
+ },
232
+ })
233
+
234
+ // embedded textual body
235
+ fastify.addSchema({
236
+ $id: makeSchemaUri("embeddedBody"),
237
+ type: "object",
238
+ required: [ "@type", "chars" ],
239
+ properties: {
240
+ "@type": {
241
+ anyOf: [
242
+ {
243
+ type: "string",
244
+ enum: embeddedBodyTypeValues
245
+ },
246
+ {
247
+ type: "array",
248
+ items: {
249
+ type: "string",
250
+ enum: embeddedBodyTypeValues
251
+ }
252
+ },
253
+ ]
254
+ },
255
+ chars: { type: "string" },
256
+ motivation: { type: "string" },
257
+ format: { type: "string" }, // should be a MimeType
258
+ }
259
+ })
260
+
261
+ fastify.addSchema({
262
+ $id: makeSchemaUri("body"),
263
+ oneOf: [
264
+ { $ref: makeSchemaUri("embeddedBody") },
265
+ { $ref: makeSchemaUri("referencedBody") },
266
+ {
267
+ type: "array",
268
+ items: {
269
+ type: "object",
270
+ anyOf: [
271
+ { $ref: makeSchemaUri("embeddedBody") },
272
+ { $ref: makeSchemaUri("referencedBody") },
273
+ ]
274
+ }
275
+ }
276
+ ]
277
+ })
278
+
279
+ // NOTE: maeData is used by `mirador-annotations-editor` (MAE) to store data specific to that mirador plugin. since MAE is external to the Aikon ecosystem, we don't define it further to avoid errors if/when its data format changes.
280
+ // we set additionalProperties: true, because otherwise, writing annotations to database will be fine, but sending responses will strip out the contents of maeData.
281
+ fastify.addSchema({
282
+ $id: makeSchemaUri("maeData"),
283
+ additionalProperties: true,
284
+ type: "object"
285
+ })
286
+
287
+ fastify.addSchema({
288
+ $id: makeSchemaUri("annotation"),
289
+ type: "object",
290
+ required: [ "@id", "@context", "@type", "motivation", "on" ],
291
+ properties: {
292
+ "@id": { type: "string" },
293
+ "@context": { type: "string" },
294
+ "@type": { type: "string", enum: [ "oa:Annotation" ] },
295
+ motivation: { $ref: makeSchemaUri("motivation") },
296
+ on: { $ref: makeSchemaUri("annotationTarget") },
297
+ // in OA, one OR the other should be use, but `oneOf` can't be used in `properties`.
298
+ resource: { $ref: makeSchemaUri("body") },
299
+ bodyValue: { type: "string" },
300
+ maeData: { $ref: makeSchemaUri("maeData") }
301
+ },
302
+ });
303
+
304
+ fastify.addSchema({
305
+ $id: makeSchemaUri("annotationList"),
306
+ type: "object",
307
+ required: [ "@id", "@type", "@context", "resources" ],
308
+ properties: {
309
+ "@id": { type: "string" },
310
+ "@context": { type: "string" },
311
+ "@type": {
312
+ type: "string",
313
+ enum: [ "sc:AnnotationList" ]
314
+ },
315
+ "resources": {
316
+ type: "array",
317
+ items: { $ref: makeSchemaUri("annotation") }
318
+ },
319
+ "prev": { type: "string" },
320
+ "next": { type: "string" },
321
+ }
322
+ });
323
+
324
+ // NOTE : not sure it's needed.
325
+ fastify.addSchema({
326
+ $id: makeSchemaUri("annotationArray"),
327
+ type: "array",
328
+ items: { $ref: makeSchemaUri("annotation") }
329
+ })
330
+
331
+ /////////////////////////////////////////////
332
+ // MANIFESTS
333
+
334
+ // internal data model for IIIF manifests, containing just what we need.
335
+ // manifests are just stored as an @id, a short ID, an array of canvas Ids. we don't need more info.
336
+ fastify.addSchema({
337
+ $id: makeSchemaUri("manifestMongo"),
338
+ type: "object",
339
+ required: [ "@id", "@type", "manifestShortId", "canvasIds" ],
340
+ properties: {
341
+ "@id": { type: "string" },
342
+ "@type": { type: "string", enum: [ "sc:Manifest" ] },
343
+ manifestShortId: { type: "string" },
344
+ canvasIds: { type: "array", items: { type: "string" } }
345
+ }
346
+ })
347
+
348
+ // minimal structure we need to work with a IIIF 2.x manifest.
349
+ fastify.addSchema({
350
+ $id: makeSchemaUri("manifestPublic"),
351
+ type: "object",
352
+ required: [ "@id", "sequences" ],
353
+ properties: {
354
+ "@id": { type: "string" },
355
+ sequences: {
356
+ type: "array",
357
+ items: {
358
+ type: "object",
359
+ required: [ "@id", "canvases" ],
360
+ properties: {
361
+ "@id": { type: "string" },
362
+ canvases: {
363
+ type: "array",
364
+ items: {
365
+ type: "object",
366
+ required: [ "@id" ],
367
+ properties: {
368
+ "@id": { type: "string" }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ }
376
+ })
377
+
378
+ /////////////////////////////////////////////
379
+ // COLLETION
380
+
381
+ fastify.addSchema({
382
+ $id: makeSchemaUri("collection"),
383
+ type: "object",
384
+ required: [ "@id", "@type", "@context", "members" ],
385
+ properties: {
386
+ "@context": { type: "string" },
387
+ "@type": { type: "string", enum: [ "sc:Collection" ] },
388
+ "@id": { type: "string" },
389
+ label: { type: "string" },
390
+ members: {
391
+ type: "array",
392
+ items: {
393
+ type: "object",
394
+ required: [ "@id" ],
395
+ properties: {
396
+ "@id": { type: "string" },
397
+ "@type": { type: "string", enum: [ "sc:Manifest" ] },
398
+ }
399
+ }
400
+ }
401
+ }
402
+ });
403
+
404
+ return fastify;
405
+ }
406
+
407
+
408
+
409
+
410
+ export default addSchemas;
@@ -0,0 +1,33 @@
1
+ import fastifyPlugin from "fastify-plugin";
2
+
3
+ import { IIIF_PRESENTATION_3, IIIF_PRESENTATION_3_CONTEXT } from "#utils/iiifUtils.js";
4
+
5
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
6
+
7
+ function addSchemas(fastify, makeSchemaUri) {
8
+ // minimal schema for IIIF manifests3, containing just what we need to process a manifest
9
+ fastify.addSchema({
10
+ $id: makeSchemaUri("manifestPublic"),
11
+ type: "object",
12
+ required: [ "@context", "id", "items" ],
13
+ properties: {
14
+ "@context": { type: "string" },
15
+ id: { type: "string" },
16
+ items: {
17
+ type: "array",
18
+ items: {
19
+ type: "object",
20
+ required: [ "id" ],
21
+ properties: {
22
+ id: { type: "string" }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ });
28
+ return fastify
29
+ }
30
+
31
+ export default addSchemas;
32
+
33
+
@@ -0,0 +1,71 @@
1
+ import fastifyPlugin from "fastify-plugin";
2
+
3
+ /**
4
+ * resolve schemas: convert all references to external schemas to embedded JsonSchemas.
5
+ *
6
+ * mongo doesn't implement the whole JSONSchema standard, and in some places in fastify (i.e., routes response schemas), references (`$ref`) are not allowed.
7
+ * in the app and in this module, our schemas are defined according to the full Fastify implementation, to use the schemas everywhere, we need to convert them.
8
+ *
9
+ * conversion primarily means converting all `$ref` references to external schemas to the JsonSchemas referenced.
10
+ *
11
+ * JSONSchema has a recursive data model in which a root schema contains nested schemas in a tree structure. in turn, our conversion function is recursive.
12
+ *
13
+ * NOTE: our conversion function DOES NOT DO ALL CONVERSIONS, only what we need.
14
+ * for example, we suppose that our schemas are built using $ref's to external schemas, and not defining sub-schemas in the `definitions` field.
15
+ *
16
+ * see: https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#omissions
17
+ */
18
+ function schemasResolver (fastify, schema) {
19
+ const funcName = schemasResolver.name;
20
+
21
+ // no need to process strings, booleans or numbers
22
+ if (typeof schema==="string" || typeof schema==="number" || typeof schema==="boolean") {
23
+ return schema
24
+ }
25
+ // convert each item in arrays
26
+ else if (Array.isArray(schema)) {
27
+ return schema.map((item) => schemasResolver(fastify, item));
28
+ }
29
+ // convert each key-value pair. this is actually the main part that will delta to the above branches.
30
+ else if (schema.constructor === Object) {
31
+ const out = {};
32
+
33
+ for (let [ k,v ] of Object.entries(schema)) {
34
+ // $id is not allowed => remove it
35
+ if (k==="$id") {
36
+ continue
37
+ }
38
+ // $refs must be resolved => replace the kv pair { $ref: schemaUri } with the corresponding schemas and process convert those schemas to mongo.
39
+ else if (k==="$ref") {
40
+ return schemasResolver(fastify, fastify.getSchema(v))
41
+ }
42
+ // convert JsonSchema integer types to bsonType: int
43
+ else if (k==="type" && v==="integer")
44
+ out.bsonType = "int";
45
+ // failsafe for other JsonSchema fields that are unimplemented by Mongo.
46
+ // NOTE: "format" is also a JsonSchema keyword, but the "format" keyword
47
+ // is used in our annotations, so we don't raise if it is encounetered.
48
+ // this filter could be fine-tuned to work only at top-level of schemas. for now, we allow "format¨.
49
+ else if ([ "$schema", "$default", "definitions" ].includes(k)) {
50
+ throw new Error(`${funcName}: JSONSchema field '${k}' conversion to Mongo schema is not implemented ! in schema: ${schema}`);
51
+ }
52
+ // it's a "normal" value => process it.
53
+ else {
54
+ out[k] = schemasResolver(fastify, v);
55
+ }
56
+ }
57
+
58
+ return out;
59
+ }
60
+ else {
61
+ throw new Error(`${funcName}: cannot process unexpected type: '${typeof k}' in schema ${schema}`);
62
+ }
63
+ }
64
+
65
+ function schemasResolverPlugin(fastify, options, done) {
66
+ fastify.decorate("schemasResolver", (schema) => schemasResolver(fastify, schema));
67
+ done();
68
+ }
69
+
70
+ export default fastifyPlugin(schemasResolverPlugin);
71
+