@internetarchive/metadata-service 0.0.0-bc6513-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.
- package/.editorconfig +29 -0
- package/.eslintrc.js +14 -0
- package/.github/workflows/ci.yml +26 -0
- package/LICENSE +661 -0
- package/README.md +64 -0
- package/demo/app-root.ts +80 -0
- package/demo/index.html +27 -0
- package/dist/demo/app-root.d.ts +11 -0
- package/dist/demo/app-root.js +71 -0
- package/dist/demo/app-root.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/src/backend/default-metadata-backend.d.ts +27 -0
- package/dist/src/backend/default-metadata-backend.js +101 -0
- package/dist/src/backend/default-metadata-backend.js.map +1 -0
- package/dist/src/backend/metadata-backend-interface.d.ts +20 -0
- package/dist/src/backend/metadata-backend-interface.js +2 -0
- package/dist/src/backend/metadata-backend-interface.js.map +1 -0
- package/dist/src/metadata-service-error.d.ts +11 -0
- package/dist/src/metadata-service-error.js +18 -0
- package/dist/src/metadata-service-error.js.map +1 -0
- package/dist/src/metadata-service-interface.d.ts +44 -0
- package/dist/src/metadata-service-interface.js +2 -0
- package/dist/src/metadata-service-interface.js.map +1 -0
- package/dist/src/metadata-service.d.ts +18 -0
- package/dist/src/metadata-service.js +43 -0
- package/dist/src/metadata-service.js.map +1 -0
- package/dist/src/models/file.d.ts +29 -0
- package/dist/src/models/file.js +95 -0
- package/dist/src/models/file.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/boolean.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/boolean.js +8 -0
- package/dist/src/models/metadata-fields/field-types/boolean.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/byte.d.ts +13 -0
- package/dist/src/models/metadata-fields/field-types/byte.js +16 -0
- package/dist/src/models/metadata-fields/field-types/byte.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/date.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/date.js +8 -0
- package/dist/src/models/metadata-fields/field-types/date.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/duration.d.ts +14 -0
- package/dist/src/models/metadata-fields/field-types/duration.js +17 -0
- package/dist/src/models/metadata-fields/field-types/duration.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/list.d.ts +25 -0
- package/dist/src/models/metadata-fields/field-types/list.js +34 -0
- package/dist/src/models/metadata-fields/field-types/list.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/mediatype.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/mediatype.js +8 -0
- package/dist/src/models/metadata-fields/field-types/mediatype.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/number.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/number.js +8 -0
- package/dist/src/models/metadata-fields/field-types/number.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/page-progression.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/page-progression.js +8 -0
- package/dist/src/models/metadata-fields/field-types/page-progression.js.map +1 -0
- package/dist/src/models/metadata-fields/field-types/string.d.ts +5 -0
- package/dist/src/models/metadata-fields/field-types/string.js +8 -0
- package/dist/src/models/metadata-fields/field-types/string.js.map +1 -0
- package/dist/src/models/metadata-fields/metadata-field.d.ts +70 -0
- package/dist/src/models/metadata-fields/metadata-field.js +67 -0
- package/dist/src/models/metadata-fields/metadata-field.js.map +1 -0
- package/dist/src/models/metadata.d.ts +169 -0
- package/dist/src/models/metadata.js +627 -0
- package/dist/src/models/metadata.js.map +1 -0
- package/dist/src/models/review.d.ts +10 -0
- package/dist/src/models/review.js +43 -0
- package/dist/src/models/review.js.map +1 -0
- package/dist/src/models/speech-music-asr-entry.d.ts +10 -0
- package/dist/src/models/speech-music-asr-entry.js +2 -0
- package/dist/src/models/speech-music-asr-entry.js.map +1 -0
- package/dist/src/responses/metadata-response.d.ts +28 -0
- package/dist/src/responses/metadata-response.js +31 -0
- package/dist/src/responses/metadata-response.js.map +1 -0
- package/dist/test/default-metadata-backend.test.d.ts +1 -0
- package/dist/test/default-metadata-backend.test.js +91 -0
- package/dist/test/default-metadata-backend.test.js.map +1 -0
- package/dist/test/metadata-service.test.d.ts +1 -0
- package/dist/test/metadata-service.test.js +99 -0
- package/dist/test/metadata-service.test.js.map +1 -0
- package/dist/test/mock-response-generator.d.ts +3 -0
- package/dist/test/mock-response-generator.js +62 -0
- package/dist/test/mock-response-generator.js.map +1 -0
- package/dist/test/models/file.test.d.ts +1 -0
- package/dist/test/models/file.test.js +9 -0
- package/dist/test/models/file.test.js.map +1 -0
- package/dist/test/models/metadata-fields/list-field.test.d.ts +1 -0
- package/dist/test/models/metadata-fields/list-field.test.js +52 -0
- package/dist/test/models/metadata-fields/list-field.test.js.map +1 -0
- package/dist/test/models/metadata-fields/metadata-field.test.d.ts +1 -0
- package/dist/test/models/metadata-fields/metadata-field.test.js +121 -0
- package/dist/test/models/metadata-fields/metadata-field.test.js.map +1 -0
- package/dist/test/models/metadata.test.d.ts +1 -0
- package/dist/test/models/metadata.test.js +10 -0
- package/dist/test/models/metadata.test.js.map +1 -0
- package/index.ts +32 -0
- package/package.json +77 -0
- package/src/backend/default-metadata-backend.ts +141 -0
- package/src/backend/metadata-backend-interface.ts +25 -0
- package/src/metadata-service-error.ts +21 -0
- package/src/metadata-service-interface.ts +51 -0
- package/src/metadata-service.ts +63 -0
- package/src/models/file.ts +105 -0
- package/src/models/metadata-fields/field-types/boolean.ts +8 -0
- package/src/models/metadata-fields/field-types/byte.ts +16 -0
- package/src/models/metadata-fields/field-types/date.ts +8 -0
- package/src/models/metadata-fields/field-types/duration.ts +17 -0
- package/src/models/metadata-fields/field-types/list.ts +44 -0
- package/src/models/metadata-fields/field-types/mediatype.ts +8 -0
- package/src/models/metadata-fields/field-types/number.ts +8 -0
- package/src/models/metadata-fields/field-types/page-progression.ts +14 -0
- package/src/models/metadata-fields/field-types/string.ts +8 -0
- package/src/models/metadata-fields/metadata-field.ts +108 -0
- package/src/models/metadata.ts +463 -0
- package/src/models/review.ts +41 -0
- package/src/models/speech-music-asr-entry.ts +10 -0
- package/src/responses/metadata-response.ts +61 -0
- package/test/default-metadata-backend.test.ts +101 -0
- package/test/metadata-service.test.ts +150 -0
- package/test/mock-response-generator.ts +62 -0
- package/test/models/file.test.ts +10 -0
- package/test/models/metadata-fields/list-field.test.ts +64 -0
- package/test/models/metadata-fields/metadata-field.test.ts +151 -0
- package/test/models/metadata.test.ts +11 -0
- package/tsconfig.json +22 -0
- package/web-test-runner.config.mjs +7 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { MetadataField } from '../../../src/models/metadata-fields/metadata-field';
|
|
3
|
+
describe('Metadata Field', () => {
|
|
4
|
+
it('can be properly instantiated with single value', () => {
|
|
5
|
+
class MockParser {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
parseValue(rawValue) {
|
|
8
|
+
return rawValue;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const parser = new MockParser();
|
|
12
|
+
const metadataField = new MetadataField(parser, 'foo');
|
|
13
|
+
expect(metadataField.rawValue).to.equal('foo');
|
|
14
|
+
expect(metadataField.value).to.equal('foo');
|
|
15
|
+
expect(metadataField.values).to.deep.equal(['foo']);
|
|
16
|
+
});
|
|
17
|
+
it('can be properly instantiated with array value', () => {
|
|
18
|
+
class MockParser {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
parseValue(rawValue) {
|
|
21
|
+
return rawValue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const parser = new MockParser();
|
|
25
|
+
const metadataField = new MetadataField(parser, ['foo', 'bar', 'baz']);
|
|
26
|
+
expect(metadataField.rawValue).to.deep.equal(['foo', 'bar', 'baz']);
|
|
27
|
+
expect(metadataField.value).to.equal('foo');
|
|
28
|
+
expect(metadataField.values).to.deep.equal(['foo', 'bar', 'baz']);
|
|
29
|
+
});
|
|
30
|
+
it('properly casts values to expected parser type for single values', () => {
|
|
31
|
+
class MockFloatParser {
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
parseValue(rawValue) {
|
|
34
|
+
return parseFloat(rawValue);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const parser = new MockFloatParser();
|
|
38
|
+
const metadataField = new MetadataField(parser, '1.3');
|
|
39
|
+
expect(metadataField.rawValue).to.equal('1.3');
|
|
40
|
+
expect(metadataField.value).to.equal(1.3);
|
|
41
|
+
expect(metadataField.values).to.deep.equal([1.3]);
|
|
42
|
+
});
|
|
43
|
+
it('properly casts values to expected parser type for array values', () => {
|
|
44
|
+
class MockFloatParser {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
parseValue(rawValue) {
|
|
47
|
+
return parseFloat(rawValue);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const parser = new MockFloatParser();
|
|
51
|
+
const metadataField = new MetadataField(parser, ['1.3', '2.4', '4.5']);
|
|
52
|
+
expect(metadataField.rawValue).to.deep.equal(['1.3', '2.4', '4.5']);
|
|
53
|
+
expect(metadataField.value).to.equal(1.3);
|
|
54
|
+
expect(metadataField.values).to.deep.equal([1.3, 2.4, 4.5]);
|
|
55
|
+
});
|
|
56
|
+
it('has no value if a raw value was not passed in', () => {
|
|
57
|
+
class MockFloatParser {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
parseValue(rawValue) {
|
|
60
|
+
return rawValue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const parser = new MockFloatParser();
|
|
64
|
+
const metadataField = new MetadataField(parser);
|
|
65
|
+
expect(metadataField.rawValue).to.equal(undefined);
|
|
66
|
+
expect(metadataField.value).to.equal(undefined);
|
|
67
|
+
expect(metadataField.values).to.deep.equal([]);
|
|
68
|
+
});
|
|
69
|
+
it('does not add value to values array if parsed value is undefined', () => {
|
|
70
|
+
class MockFloatParser {
|
|
71
|
+
parseValue(rawValue) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const parser = new MockFloatParser();
|
|
76
|
+
const metadataField = new MetadataField(parser);
|
|
77
|
+
expect(metadataField.rawValue).to.equal(undefined);
|
|
78
|
+
expect(metadataField.value).to.equal(undefined);
|
|
79
|
+
expect(metadataField.values).to.deep.equal([]);
|
|
80
|
+
});
|
|
81
|
+
it('handles falsy `0` return values properly', () => {
|
|
82
|
+
class MockFloatParser {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
parseValue(rawValue) {
|
|
85
|
+
return rawValue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const parser = new MockFloatParser();
|
|
89
|
+
const metadataField = new MetadataField(parser, 0);
|
|
90
|
+
expect(metadataField.rawValue).to.equal(0);
|
|
91
|
+
expect(metadataField.value).to.equal(0);
|
|
92
|
+
expect(metadataField.values).to.deep.equal([0]);
|
|
93
|
+
});
|
|
94
|
+
it('handles falsy `false` return values properly', () => {
|
|
95
|
+
class MockFloatParser {
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
parseValue(rawValue) {
|
|
98
|
+
return rawValue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const parser = new MockFloatParser();
|
|
102
|
+
const metadataField = new MetadataField(parser, false);
|
|
103
|
+
expect(metadataField.rawValue).to.equal(false);
|
|
104
|
+
expect(metadataField.value).to.equal(false);
|
|
105
|
+
expect(metadataField.values).to.deep.equal([false]);
|
|
106
|
+
});
|
|
107
|
+
it('handles falsy empty string return values properly', () => {
|
|
108
|
+
class MockFloatParser {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
parseValue(rawValue) {
|
|
111
|
+
return rawValue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const parser = new MockFloatParser();
|
|
115
|
+
const metadataField = new MetadataField(parser, '');
|
|
116
|
+
expect(metadataField.rawValue).to.equal('');
|
|
117
|
+
expect(metadataField.value).to.equal('');
|
|
118
|
+
expect(metadataField.values).to.deep.equal(['']);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=metadata-field.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-field.test.js","sourceRoot":"","sources":["../../../../test/models/metadata-fields/metadata-field.test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,oDAAoD,CAAC;AAEnF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,UAAU;YACd,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEvD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,UAAU;YACd,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAEvE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEvD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAEvE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QAEhD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,eAAe;YACnB,UAAU,CAAC,QAA6B;gBACtC,OAAO,SAAS,CAAC;YACnB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QAEhD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAEnD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEvD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,eAAe;YACnB,8DAA8D;YAC9D,UAAU,CAAC,QAAa;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF;QAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEpD,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import {\n FieldParserInterface,\n FieldParserRawValue,\n} from '@internetarchive/field-parsers';\nimport { expect } from '@open-wc/testing';\nimport { MetadataField } from '../../../src/models/metadata-fields/metadata-field';\n\ndescribe('Metadata Field', () => {\n it('can be properly instantiated with single value', () => {\n class MockParser implements FieldParserInterface<string> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): string {\n return rawValue;\n }\n }\n\n const parser = new MockParser();\n const metadataField = new MetadataField(parser, 'foo');\n\n expect(metadataField.rawValue).to.equal('foo');\n expect(metadataField.value).to.equal('foo');\n expect(metadataField.values).to.deep.equal(['foo']);\n });\n\n it('can be properly instantiated with array value', () => {\n class MockParser implements FieldParserInterface<string> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): string {\n return rawValue;\n }\n }\n\n const parser = new MockParser();\n const metadataField = new MetadataField(parser, ['foo', 'bar', 'baz']);\n\n expect(metadataField.rawValue).to.deep.equal(['foo', 'bar', 'baz']);\n expect(metadataField.value).to.equal('foo');\n expect(metadataField.values).to.deep.equal(['foo', 'bar', 'baz']);\n });\n\n it('properly casts values to expected parser type for single values', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return parseFloat(rawValue);\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser, '1.3');\n\n expect(metadataField.rawValue).to.equal('1.3');\n expect(metadataField.value).to.equal(1.3);\n expect(metadataField.values).to.deep.equal([1.3]);\n });\n\n it('properly casts values to expected parser type for array values', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return parseFloat(rawValue);\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser, ['1.3', '2.4', '4.5']);\n\n expect(metadataField.rawValue).to.deep.equal(['1.3', '2.4', '4.5']);\n expect(metadataField.value).to.equal(1.3);\n expect(metadataField.values).to.deep.equal([1.3, 2.4, 4.5]);\n });\n\n it('has no value if a raw value was not passed in', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return rawValue;\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser);\n\n expect(metadataField.rawValue).to.equal(undefined);\n expect(metadataField.value).to.equal(undefined);\n expect(metadataField.values).to.deep.equal([]);\n });\n\n it('does not add value to values array if parsed value is undefined', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n parseValue(rawValue: FieldParserRawValue): number | undefined {\n return undefined;\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser);\n\n expect(metadataField.rawValue).to.equal(undefined);\n expect(metadataField.value).to.equal(undefined);\n expect(metadataField.values).to.deep.equal([]);\n });\n\n it('handles falsy `0` return values properly', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return rawValue;\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser, 0);\n\n expect(metadataField.rawValue).to.equal(0);\n expect(metadataField.value).to.equal(0);\n expect(metadataField.values).to.deep.equal([0]);\n });\n\n it('handles falsy `false` return values properly', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return rawValue;\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser, false);\n\n expect(metadataField.rawValue).to.equal(false);\n expect(metadataField.value).to.equal(false);\n expect(metadataField.values).to.deep.equal([false]);\n });\n\n it('handles falsy empty string return values properly', () => {\n class MockFloatParser implements FieldParserInterface<number> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseValue(rawValue: any): number {\n return rawValue;\n }\n }\n\n const parser = new MockFloatParser();\n const metadataField = new MetadataField(parser, '');\n\n expect(metadataField.rawValue).to.equal('');\n expect(metadataField.value).to.equal('');\n expect(metadataField.values).to.deep.equal(['']);\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { Metadata } from '../../src/models/metadata';
|
|
3
|
+
describe('Metadata', () => {
|
|
4
|
+
it('properly instantiates metadata with identifier', async () => {
|
|
5
|
+
const json = { identifier: 'foo', collection: 'bar' };
|
|
6
|
+
const metadata = new Metadata(json);
|
|
7
|
+
expect(metadata.identifier).to.equal('foo');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=metadata.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.test.js","sourceRoot":"","sources":["../../../test/models/metadata.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,IAAI,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { expect } from '@open-wc/testing';\n\nimport { Metadata } from '../../src/models/metadata';\n\ndescribe('Metadata', () => {\n it('properly instantiates metadata with identifier', async () => {\n const json = { identifier: 'foo', collection: 'bar' };\n const metadata = new Metadata(json);\n expect(metadata.identifier).to.equal('foo');\n });\n});\n"]}
|
package/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { Metadata } from './src/models/metadata';
|
|
2
|
+
export { File } from './src/models/file';
|
|
3
|
+
export { Review } from './src/models/review';
|
|
4
|
+
export { SpeechMusicASREntry } from './src/models/speech-music-asr-entry';
|
|
5
|
+
|
|
6
|
+
export { DateField } from './src/models/metadata-fields/field-types/date';
|
|
7
|
+
|
|
8
|
+
export { NumberField } from './src/models/metadata-fields/field-types/number';
|
|
9
|
+
|
|
10
|
+
export { StringField } from './src/models/metadata-fields/field-types/string';
|
|
11
|
+
|
|
12
|
+
export { BooleanField } from './src/models/metadata-fields/field-types/boolean';
|
|
13
|
+
|
|
14
|
+
export { ByteField } from './src/models/metadata-fields/field-types/byte';
|
|
15
|
+
|
|
16
|
+
export { DurationField } from './src/models/metadata-fields/field-types/duration';
|
|
17
|
+
|
|
18
|
+
export { PageProgressionField } from './src/models/metadata-fields/field-types/page-progression';
|
|
19
|
+
|
|
20
|
+
export { MediaTypeField } from './src/models/metadata-fields/field-types/mediatype';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
MetadataFieldInterface,
|
|
24
|
+
MetadataField,
|
|
25
|
+
} from './src/models/metadata-fields/metadata-field';
|
|
26
|
+
|
|
27
|
+
export { MetadataResponse } from './src/responses/metadata-response';
|
|
28
|
+
|
|
29
|
+
export { DefaultMetadataBackend } from './src/backend/default-metadata-backend';
|
|
30
|
+
export { MetadataServiceInterface } from './src/metadata-service-interface';
|
|
31
|
+
export { MetadataService } from './src/metadata-service';
|
|
32
|
+
export { MetadataServiceError } from './src/metadata-service-error';
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@internetarchive/metadata-service",
|
|
3
|
+
"version": "0.0.0-bc6513-0",
|
|
4
|
+
"description": "A service for fetching metadata about items in the Internet Archive",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "concurrently --kill-others --names tsc,web-dev-server \"npm run tsc:watch\" \"web-dev-server --app-index demo/index.html --node-resolve --open --watch\"",
|
|
14
|
+
"tsc:watch": "tsc --watch",
|
|
15
|
+
"docs": "typedoc ./index.ts",
|
|
16
|
+
"prepare": "tsc",
|
|
17
|
+
"lint:eslint": "eslint --ext .ts,.html . --ignore-path .gitignore",
|
|
18
|
+
"format:eslint": "eslint --ext .ts,.html . --fix --ignore-path .gitignore",
|
|
19
|
+
"lint:prettier": "prettier \"**/*.js\" \"**/*.ts\" --check --ignore-path .gitignore",
|
|
20
|
+
"format:prettier": "prettier \"**/*.js\" \"**/*.ts\" --write --ignore-path .gitignore",
|
|
21
|
+
"lint": "npm run lint:eslint && npm run lint:prettier",
|
|
22
|
+
"format": "npm run format:eslint && npm run format:prettier",
|
|
23
|
+
"circular": "madge --circular --extensions ts .",
|
|
24
|
+
"test": "tsc && npm run lint && npm run circular && web-test-runner --coverage",
|
|
25
|
+
"test:watch": "web-test-runner --watch",
|
|
26
|
+
"codecov": "npx codecov"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@open-wc/eslint-config": "^4.2.0",
|
|
30
|
+
"@open-wc/testing": "^2.0.0",
|
|
31
|
+
"@types/node": "13.11.1",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
|
33
|
+
"@typescript-eslint/parser": "^4.16.1",
|
|
34
|
+
"@web/dev-server": "^0.0.12",
|
|
35
|
+
"@web/test-runner": "^0.13.23",
|
|
36
|
+
"concurrently": "^5.1.0",
|
|
37
|
+
"eslint": "^7.21.0",
|
|
38
|
+
"eslint-config-prettier": "^6.11.0",
|
|
39
|
+
"husky": "^1.0.0",
|
|
40
|
+
"lint-staged": "^10.0.0",
|
|
41
|
+
"lit-element": "^2.4.0",
|
|
42
|
+
"lit-html": "^1.3.0",
|
|
43
|
+
"madge": "^3.12.0",
|
|
44
|
+
"prettier": "^2.0.4",
|
|
45
|
+
"tslib": "^1.14.1",
|
|
46
|
+
"typedoc": "^0.20.29",
|
|
47
|
+
"typescript": "^4.2.2"
|
|
48
|
+
},
|
|
49
|
+
"eslintConfig": {
|
|
50
|
+
"extends": [
|
|
51
|
+
"@open-wc/eslint-config",
|
|
52
|
+
"eslint-config-prettier"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"prettier": {
|
|
56
|
+
"singleQuote": true,
|
|
57
|
+
"arrowParens": "avoid"
|
|
58
|
+
},
|
|
59
|
+
"husky": {
|
|
60
|
+
"hooks": {
|
|
61
|
+
"pre-commit": "lint-staged"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"lint-staged": {
|
|
65
|
+
"*.ts": [
|
|
66
|
+
"eslint --fix",
|
|
67
|
+
"prettier --write",
|
|
68
|
+
"git add"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@internetarchive/field-parsers": "^0.1.3",
|
|
73
|
+
"@internetarchive/result-type": "^0.0.1",
|
|
74
|
+
"decorator-cache-getter": "^1.0.0",
|
|
75
|
+
"typescript-memoize": "^1.1.0"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { Result } from '@internetarchive/result-type';
|
|
3
|
+
import { MetadataBackendInterface } from './metadata-backend-interface';
|
|
4
|
+
import {
|
|
5
|
+
MetadataServiceError,
|
|
6
|
+
MetadataServiceErrorType,
|
|
7
|
+
} from '../metadata-service-error';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The DefaultSearchBackend performs a `window.fetch` request to archive.org
|
|
11
|
+
*/
|
|
12
|
+
export class DefaultMetadataBackend implements MetadataBackendInterface {
|
|
13
|
+
private baseUrl: string;
|
|
14
|
+
|
|
15
|
+
private includeCredentials: boolean;
|
|
16
|
+
|
|
17
|
+
private requestScope?: string;
|
|
18
|
+
|
|
19
|
+
constructor(options?: {
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
includeCredentials?: boolean;
|
|
22
|
+
scope?: string;
|
|
23
|
+
}) {
|
|
24
|
+
this.baseUrl = options?.baseUrl ?? 'archive.org';
|
|
25
|
+
|
|
26
|
+
if (options?.includeCredentials !== undefined) {
|
|
27
|
+
this.includeCredentials = options.includeCredentials;
|
|
28
|
+
} else {
|
|
29
|
+
// include credentials if the request is coming from an archive.org domain
|
|
30
|
+
// since credentialed requests are only allowed from archive.org domains
|
|
31
|
+
// due to CORS restrictions, see
|
|
32
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
|
|
33
|
+
this.includeCredentials =
|
|
34
|
+
window.location.href.match(/^https?:\/\/.*archive\.org(:[0-9]+)?/) !==
|
|
35
|
+
null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (options?.scope !== undefined) {
|
|
39
|
+
this.requestScope = options.scope;
|
|
40
|
+
} else {
|
|
41
|
+
const currentUrl = new URL(window.location.href);
|
|
42
|
+
const scope = currentUrl.searchParams.get('scope');
|
|
43
|
+
if (scope) {
|
|
44
|
+
this.requestScope = scope;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @inheritdoc */
|
|
50
|
+
async fetchMetadata(
|
|
51
|
+
identifier: string,
|
|
52
|
+
keypath?: string
|
|
53
|
+
): Promise<Result<any, MetadataServiceError>> {
|
|
54
|
+
const path = keypath ? `/${keypath}` : '';
|
|
55
|
+
const url = `https://${this.baseUrl}/metadata/${identifier}${path}`;
|
|
56
|
+
// the metadata endpoint doesn't currently support credentialed requests
|
|
57
|
+
// so don't include credentials until that is fixed
|
|
58
|
+
return this.fetchUrl(url, {
|
|
59
|
+
requestOptions: {
|
|
60
|
+
credentials: 'omit',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fires a request to the URL (with this backend's options applied) and
|
|
67
|
+
* asynchronously returns a Result object containing either the raw response
|
|
68
|
+
* JSON or a MetadataServiceError.
|
|
69
|
+
*/
|
|
70
|
+
protected async fetchUrl(
|
|
71
|
+
url: string,
|
|
72
|
+
options?: {
|
|
73
|
+
requestOptions?: RequestInit;
|
|
74
|
+
}
|
|
75
|
+
): Promise<Result<any, MetadataServiceError>> {
|
|
76
|
+
const finalUrl = new URL(url);
|
|
77
|
+
if (this.requestScope) {
|
|
78
|
+
finalUrl.searchParams.set('scope', this.requestScope);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let response: Response;
|
|
82
|
+
// first try the fetch and return a networkError if it fails
|
|
83
|
+
try {
|
|
84
|
+
const fetchOptions = options?.requestOptions ?? {
|
|
85
|
+
credentials: this.includeCredentials ? 'include' : 'same-origin',
|
|
86
|
+
};
|
|
87
|
+
response = await fetch(finalUrl.href, fetchOptions);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const message =
|
|
90
|
+
err instanceof Error
|
|
91
|
+
? err.message
|
|
92
|
+
: typeof err === 'string'
|
|
93
|
+
? err
|
|
94
|
+
: 'Unknown error';
|
|
95
|
+
return this.getErrorResult(
|
|
96
|
+
MetadataServiceErrorType.networkError,
|
|
97
|
+
message
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// then try json decoding and return a decodingError if it fails
|
|
102
|
+
try {
|
|
103
|
+
const json = await response.json();
|
|
104
|
+
// the advanced search endpoint doesn't return an HTTP Error 400
|
|
105
|
+
// and instead returns an HTTP 200 with an `error` key in the payload
|
|
106
|
+
const error = json['error'];
|
|
107
|
+
if (error) {
|
|
108
|
+
const forensics = json['forensics'];
|
|
109
|
+
return this.getErrorResult(
|
|
110
|
+
MetadataServiceErrorType.searchEngineError,
|
|
111
|
+
error,
|
|
112
|
+
forensics
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
// success
|
|
116
|
+
return { success: json };
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const message =
|
|
120
|
+
err instanceof Error
|
|
121
|
+
? err.message
|
|
122
|
+
: typeof err === 'string'
|
|
123
|
+
? err
|
|
124
|
+
: 'Unknown error';
|
|
125
|
+
return this.getErrorResult(
|
|
126
|
+
MetadataServiceErrorType.decodingError,
|
|
127
|
+
message
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private getErrorResult(
|
|
133
|
+
errorType: MetadataServiceErrorType,
|
|
134
|
+
message?: string,
|
|
135
|
+
details?: any
|
|
136
|
+
): Result<any, MetadataServiceError> {
|
|
137
|
+
const error = new MetadataServiceError(errorType, message, details);
|
|
138
|
+
const result = { error };
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { Result } from '@internetarchive/result-type';
|
|
3
|
+
import { MetadataServiceError } from '../metadata-service-error';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An interface to provide the network layer to the `MetadataService`.
|
|
7
|
+
*
|
|
8
|
+
* Objects implementing this interface are responsible for making calls to the Internet Archive
|
|
9
|
+
* `metadata` endpoint or otherwise providing a similar reponse in JSON format.
|
|
10
|
+
*
|
|
11
|
+
* @export
|
|
12
|
+
* @interface MetadataBackendInterface
|
|
13
|
+
*/
|
|
14
|
+
export interface MetadataBackendInterface {
|
|
15
|
+
/**
|
|
16
|
+
* Fetch metadata for a single item with an optional keypath
|
|
17
|
+
*
|
|
18
|
+
* @param identifier
|
|
19
|
+
* @param keypath
|
|
20
|
+
*/
|
|
21
|
+
fetchMetadata(
|
|
22
|
+
identifier: string,
|
|
23
|
+
keypath?: string
|
|
24
|
+
): Promise<Result<any, MetadataServiceError>>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
export enum MetadataServiceErrorType {
|
|
4
|
+
networkError = 'MetadataService.NetworkError',
|
|
5
|
+
itemNotFound = 'MetadataService.ItemNotFound',
|
|
6
|
+
decodingError = 'MetadataService.DecodingError',
|
|
7
|
+
searchEngineError = 'MetadataService.SearchEngineError',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class MetadataServiceError extends Error {
|
|
11
|
+
type: MetadataServiceErrorType;
|
|
12
|
+
|
|
13
|
+
details?: any;
|
|
14
|
+
|
|
15
|
+
constructor(type: MetadataServiceErrorType, message?: string, details?: any) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = type;
|
|
18
|
+
this.type = type;
|
|
19
|
+
this.details = details;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { MetadataResponse } from './responses/metadata-response';
|
|
2
|
+
import type { Result } from '@internetarchive/result-type';
|
|
3
|
+
import type { MetadataServiceError } from './metadata-service-error';
|
|
4
|
+
|
|
5
|
+
export interface MetadataServiceInterface {
|
|
6
|
+
/**
|
|
7
|
+
* Fetch metadata for a given identifier
|
|
8
|
+
*
|
|
9
|
+
* @param {string} identifier
|
|
10
|
+
* @returns {Promise<Result<MetadataResponse, MetadataServiceError>>}
|
|
11
|
+
*/
|
|
12
|
+
fetchMetadata(
|
|
13
|
+
identifier: string
|
|
14
|
+
): Promise<Result<MetadataResponse, MetadataServiceError>>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch the metadata value for a given identifier and keypath
|
|
18
|
+
*
|
|
19
|
+
* The response from this request can take any form, object, array, string, etc.
|
|
20
|
+
* depending on the query. You can provide return typing in the response by
|
|
21
|
+
* specifying the type. Note, there is no automatic type conversion since it can be anything.
|
|
22
|
+
*
|
|
23
|
+
* For example:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const collection = await searchService.fetchMetadataValue<string>('goody', 'metadata/collection/0');
|
|
27
|
+
* console.debug('collection:', collection); => 'Goody Collection'
|
|
28
|
+
*
|
|
29
|
+
* const files_count = await searchService.fetchMetadataValue<number>('goody', 'files_count');
|
|
30
|
+
* console.debug('files_count:', files_count); => 12
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Keypath examples:
|
|
34
|
+
*
|
|
35
|
+
* /metadata/:identifier/metadata // returns the entire metadata object
|
|
36
|
+
* /metadata/:identifier/server // returns the server for the given identifier
|
|
37
|
+
* /metadata/:identifier/files_count
|
|
38
|
+
* /metadata/:identifier/files?start=1&count=2 // query for files
|
|
39
|
+
* /metadata/:identifier/metadata/collection // all collections
|
|
40
|
+
* /metadata/:identifier/metadata/collection/0 // first collection
|
|
41
|
+
* /metadata/:identifier/metadata/title
|
|
42
|
+
* /metadata/:identifier/files/0/name // first file name
|
|
43
|
+
*
|
|
44
|
+
* @param identifier
|
|
45
|
+
* @param keypath
|
|
46
|
+
*/
|
|
47
|
+
fetchMetadataValue<T>(
|
|
48
|
+
identifier: string,
|
|
49
|
+
keypath: string
|
|
50
|
+
): Promise<Result<T, MetadataServiceError>>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { MetadataResponse } from './responses/metadata-response';
|
|
2
|
+
import {
|
|
3
|
+
MetadataServiceError,
|
|
4
|
+
MetadataServiceErrorType,
|
|
5
|
+
} from './metadata-service-error';
|
|
6
|
+
import type { MetadataServiceInterface } from './metadata-service-interface';
|
|
7
|
+
import type { Result } from '@internetarchive/result-type';
|
|
8
|
+
import { DefaultMetadataBackend } from './backend/default-metadata-backend';
|
|
9
|
+
import { MetadataBackendInterface } from './backend/metadata-backend-interface';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The Metadata Service is responsible for taking the raw response provided by
|
|
13
|
+
* the backend and modeling it as a `MetadataResponse` object.
|
|
14
|
+
*/
|
|
15
|
+
export class MetadataService implements MetadataServiceInterface {
|
|
16
|
+
public static default: MetadataServiceInterface = new MetadataService(
|
|
17
|
+
new DefaultMetadataBackend()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
private backend: MetadataBackendInterface;
|
|
21
|
+
|
|
22
|
+
constructor(backend: MetadataBackendInterface) {
|
|
23
|
+
this.backend = backend;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @inheritdoc */
|
|
27
|
+
async fetchMetadata(
|
|
28
|
+
identifier: string
|
|
29
|
+
): Promise<Result<MetadataResponse, MetadataServiceError>> {
|
|
30
|
+
const rawResponse = await this.backend.fetchMetadata(identifier);
|
|
31
|
+
if (rawResponse.error) {
|
|
32
|
+
return rawResponse;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (rawResponse.success?.metadata === undefined) {
|
|
36
|
+
return {
|
|
37
|
+
error: new MetadataServiceError(MetadataServiceErrorType.itemNotFound),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const modeledResponse = new MetadataResponse(rawResponse.success);
|
|
42
|
+
return { success: modeledResponse };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @inheritdoc */
|
|
46
|
+
async fetchMetadataValue<T>(
|
|
47
|
+
identifier: string,
|
|
48
|
+
keypath: string
|
|
49
|
+
): Promise<Result<T, MetadataServiceError>> {
|
|
50
|
+
const result = await this.backend.fetchMetadata(identifier, keypath);
|
|
51
|
+
if (result.error) {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (result.success?.result === undefined) {
|
|
56
|
+
return {
|
|
57
|
+
error: new MetadataServiceError(MetadataServiceErrorType.itemNotFound),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { success: result.success.result };
|
|
62
|
+
}
|
|
63
|
+
}
|