@internetarchive/metadata-service 1.0.6 → 1.2.0-beta.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/README.md +18 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/backend/default-metadata-backend.d.ts +8 -0
- package/dist/src/backend/default-metadata-backend.js +32 -0
- package/dist/src/backend/default-metadata-backend.js.map +1 -1
- package/dist/src/backend/metadata-backend-interface.d.ts +14 -0
- package/dist/src/backend/metadata-backend-interface.js.map +1 -1
- package/dist/src/metadata-patch.d.ts +21 -0
- package/dist/src/metadata-patch.js +37 -0
- package/dist/src/metadata-patch.js.map +1 -0
- package/dist/src/metadata-service-error.d.ts +3 -1
- package/dist/src/metadata-service-error.js +2 -0
- package/dist/src/metadata-service-error.js.map +1 -1
- package/dist/src/metadata-service-interface.d.ts +42 -0
- package/dist/src/metadata-service-interface.js.map +1 -1
- package/dist/src/metadata-service.d.ts +6 -1
- package/dist/src/metadata-service.js +28 -0
- package/dist/src/metadata-service.js.map +1 -1
- package/dist/src/responses/metadata-response.d.ts +22 -0
- package/dist/src/responses/metadata-response.js +5 -0
- package/dist/src/responses/metadata-response.js.map +1 -1
- package/dist/test/default-metadata-backend.test.js +52 -0
- package/dist/test/default-metadata-backend.test.js.map +1 -1
- package/dist/test/metadata-patch.test.js +41 -0
- package/dist/test/metadata-patch.test.js.map +1 -0
- package/dist/test/metadata-service-write.test.js +96 -0
- package/dist/test/metadata-service-write.test.js.map +1 -0
- package/dist/test/mock-response-generator.js +11 -0
- package/dist/test/mock-response-generator.js.map +1 -1
- package/dist/test/responses/metadata-response.test.d.ts +1 -0
- package/dist/test/responses/metadata-response.test.js +48 -0
- package/dist/test/responses/metadata-response.test.js.map +1 -0
- package/index.ts +13 -1
- package/package.json +2 -2
- package/src/backend/default-metadata-backend.ts +47 -0
- package/src/backend/metadata-backend-interface.ts +19 -0
- package/src/metadata-patch.ts +53 -0
- package/src/metadata-service-error.ts +2 -0
- package/src/metadata-service-interface.ts +56 -0
- package/src/metadata-service.ts +57 -1
- package/src/responses/metadata-response.ts +34 -0
- package/test/default-metadata-backend.test.ts +66 -0
- package/test/metadata-patch.test.ts +53 -0
- package/test/metadata-service-write.test.ts +138 -0
- package/test/mock-response-generator.ts +11 -0
- package/test/responses/metadata-response.test.ts +55 -0
- package/.claude/settings.local.json +0 -15
- package/dist/src/models/file.d.ts +0 -29
- package/dist/src/models/file.js +0 -104
- package/dist/src/models/file.js.map +0 -1
- package/dist/src/models/review.d.ts +0 -11
- package/dist/src/models/review.js +0 -46
- package/dist/src/models/review.js.map +0 -1
- package/dist/src/models/speech-music-asr-entry.d.ts +0 -10
- package/dist/src/models/speech-music-asr-entry.js +0 -2
- package/dist/src/models/speech-music-asr-entry.js.map +0 -1
- package/dist/src/models/task.d.ts +0 -11
- package/dist/src/models/task.js +0 -2
- package/dist/src/models/task.js.map +0 -1
- package/dist/test/models/file.test.js +0 -57
- package/dist/test/models/file.test.js.map +0 -1
- package/dist/test/models/review.test.js +0 -53
- package/dist/test/models/review.test.js.map +0 -1
- /package/dist/test/{models/file.test.d.ts → metadata-patch.test.d.ts} +0 -0
- /package/dist/test/{models/review.test.d.ts → metadata-service-write.test.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-metadata-backend.test.js","sourceRoot":"","sources":["../../test/default-metadata-backend.test.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n\nimport { expect } from '@open-wc/testing';\nimport { DefaultMetadataBackend } from '../src/backend/default-metadata-backend';\nimport { MetadataServiceErrorType } from '../src/metadata-service-error';\n\ndescribe('DefaultMetadataBackend', () => {\n it('can fetch metadata', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n return new Promise(resolve => {\n const response = new Response('{ \"foo\": \"bar\" }');\n resolve(response);\n });\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.success?.foo).to.equal('bar');\n window.fetch = fetchBackup;\n });\n\n it('returns a networkError if theres a problem fetching using String type', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n throw 'network error';\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.networkError);\n expect(result.error?.message).to.equal('network error');\n window.fetch = fetchBackup;\n });\n\n it('returns a networkError if theres a problem fetching using Error type', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n throw new Error('network error');\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.networkError);\n expect(result.error?.message).to.equal('network error');\n window.fetch = fetchBackup;\n });\n\n it('returns a decodingError if theres a problem decoding the json', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.decodingError);\n window.fetch = fetchBackup;\n });\n\n it('appends the scope if provided', async () => {\n const fetchBackup = window.fetch;\n let urlCalled = '';\n window.fetch = (\n input: RequestInfo | URL,\n init?: RequestInit | undefined,\n ): Promise<Response> => {\n urlCalled = input.toString();\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend({\n scope: 'foo',\n });\n const result = await backend.fetchMetadata('foo');\n expect(urlCalled.includes('scope=foo')).to.be.true;\n window.fetch = fetchBackup;\n });\n\n it('credentials for metadata endpoint', async () => {\n const fetchBackup = window.fetch;\n let urlCalled: RequestInfo | URL;\n let urlConfig: RequestInit | undefined;\n window.fetch = (\n input: RequestInfo | URL,\n init?: RequestInit | undefined,\n ): Promise<Response> => {\n urlCalled = input;\n urlConfig = init;\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend({\n scope: 'foo',\n includeCredentials: true,\n });\n await backend.fetchMetadata('foo');\n expect(urlConfig?.credentials).to.equal('include');\n window.fetch = fetchBackup;\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"default-metadata-backend.test.js","sourceRoot":"","sources":["../../test/default-metadata-backend.test.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n\nimport { expect } from '@open-wc/testing';\nimport { DefaultMetadataBackend } from '../src/backend/default-metadata-backend';\nimport { MetadataServiceErrorType } from '../src/metadata-service-error';\n\ndescribe('DefaultMetadataBackend', () => {\n it('can fetch metadata', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n return new Promise(resolve => {\n const response = new Response('{ \"foo\": \"bar\" }');\n resolve(response);\n });\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.success?.foo).to.equal('bar');\n window.fetch = fetchBackup;\n });\n\n it('returns a networkError if theres a problem fetching using String type', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n throw 'network error';\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.networkError);\n expect(result.error?.message).to.equal('network error');\n window.fetch = fetchBackup;\n });\n\n it('returns a networkError if theres a problem fetching using Error type', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n throw new Error('network error');\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.networkError);\n expect(result.error?.message).to.equal('network error');\n window.fetch = fetchBackup;\n });\n\n it('returns a decodingError if theres a problem decoding the json', async () => {\n const fetchBackup = window.fetch;\n window.fetch = (): Promise<Response> => {\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.fetchMetadata('foo');\n expect(result.error?.type).to.equal(MetadataServiceErrorType.decodingError);\n window.fetch = fetchBackup;\n });\n\n it('appends the scope if provided', async () => {\n const fetchBackup = window.fetch;\n let urlCalled = '';\n window.fetch = (\n input: RequestInfo | URL,\n init?: RequestInit | undefined,\n ): Promise<Response> => {\n urlCalled = input.toString();\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend({\n scope: 'foo',\n });\n const result = await backend.fetchMetadata('foo');\n expect(urlCalled.includes('scope=foo')).to.be.true;\n window.fetch = fetchBackup;\n });\n\n it('credentials for metadata endpoint', async () => {\n const fetchBackup = window.fetch;\n let urlCalled: RequestInfo | URL;\n let urlConfig: RequestInit | undefined;\n window.fetch = (\n input: RequestInfo | URL,\n init?: RequestInit | undefined,\n ): Promise<Response> => {\n urlCalled = input;\n urlConfig = init;\n const response = new Response('boop');\n return new Promise(resolve => resolve(response));\n };\n\n const backend = new DefaultMetadataBackend({\n scope: 'foo',\n includeCredentials: true,\n });\n await backend.fetchMetadata('foo');\n expect(urlConfig?.credentials).to.equal('include');\n window.fetch = fetchBackup;\n });\n});\n\ndescribe('DefaultMetadataBackend writeMetadata', () => {\n const fetchBackup = window.fetch;\n\n afterEach(() => {\n window.fetch = fetchBackup;\n });\n\n it('posts the patch as a form to the item endpoint', async () => {\n let requestUrl = '';\n let requestInit: RequestInit | undefined;\n window.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n requestUrl = String(input);\n requestInit = init;\n return new Response('{ \"success\": true, \"task_id\": 7, \"log\": \"x\" }');\n };\n\n const backend = new DefaultMetadataBackend({ includeCredentials: true });\n const patch = [{ op: 'replace' as const, path: '/title', value: 'New' }];\n const result = await backend.writeMetadata('foo', 'metadata', patch);\n\n expect(result.success?.task_id).to.equal(7);\n expect(requestUrl).to.equal('https://archive.org/metadata/foo');\n expect(requestInit?.method).to.equal('POST');\n expect(requestInit?.credentials).to.equal('include');\n const body = requestInit?.body as URLSearchParams;\n expect(body.get('-target')).to.equal('metadata');\n expect(JSON.parse(body.get('-patch') ?? '')).to.deep.equal(patch);\n });\n\n it('returns a writeError with MDAPI’s message when the write is rejected', async () => {\n window.fetch = async (): Promise<Response> =>\n new Response('{ \"success\": false, \"error\": \"Authorization failed\" }', {\n status: 401,\n });\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.writeMetadata('foo', 'metadata', []);\n\n expect(result.error?.type).to.equal(MetadataServiceErrorType.writeError);\n expect(result.error?.message).to.equal('Authorization failed');\n });\n\n it('returns a writeError when MDAPI does not report success', async () => {\n window.fetch = async (): Promise<Response> => new Response('{}');\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.writeMetadata('foo', 'metadata', []);\n\n expect(result.error?.type).to.equal(MetadataServiceErrorType.writeError);\n });\n\n it('returns a networkError when the request fails', async () => {\n window.fetch = async (): Promise<Response> => {\n throw new Error('offline');\n };\n\n const backend = new DefaultMetadataBackend();\n const result = await backend.writeMetadata('foo', 'metadata', []);\n\n expect(result.error?.type).to.equal(MetadataServiceErrorType.networkError);\n });\n});\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { buildMetadataFieldPatch } from '../src/metadata-patch';
|
|
3
|
+
describe('buildMetadataFieldPatch', () => {
|
|
4
|
+
it('replaces an existing field', () => {
|
|
5
|
+
expect(buildMetadataFieldPatch('title', 'New', 'Old')).to.deep.equal([
|
|
6
|
+
{ op: 'replace', path: '/title', value: 'New' },
|
|
7
|
+
]);
|
|
8
|
+
});
|
|
9
|
+
it('adds a field that is absent', () => {
|
|
10
|
+
expect(buildMetadataFieldPatch('volume', ['2'], undefined)).to.deep.equal([
|
|
11
|
+
{ op: 'add', path: '/volume', value: '2' },
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
it('writes a single-entry list as a bare string', () => {
|
|
15
|
+
expect(buildMetadataFieldPatch('subject', ['one'], ['one', 'two'])).to.deep.equal([{ op: 'replace', path: '/subject', value: 'one' }]);
|
|
16
|
+
});
|
|
17
|
+
it('keeps multiple values as a list', () => {
|
|
18
|
+
expect(buildMetadataFieldPatch('subject', ['a', 'b'], 'a')).to.deep.equal([
|
|
19
|
+
{ op: 'replace', path: '/subject', value: ['a', 'b'] },
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
it('removes a field set to nothing', () => {
|
|
23
|
+
expect(buildMetadataFieldPatch('notes', [], 'old')).to.deep.equal([
|
|
24
|
+
{ op: 'remove', path: '/notes' },
|
|
25
|
+
]);
|
|
26
|
+
expect(buildMetadataFieldPatch('notes', '', ['a', 'b'])).to.deep.equal([
|
|
27
|
+
{ op: 'remove', path: '/notes' },
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
it('returns an empty patch when nothing changes', () => {
|
|
31
|
+
expect(buildMetadataFieldPatch('title', ['Same'], 'Same')).to.deep.equal([]);
|
|
32
|
+
expect(buildMetadataFieldPatch('title', 'Same', ['Same'])).to.deep.equal([]);
|
|
33
|
+
expect(buildMetadataFieldPatch('notes', [], undefined)).to.deep.equal([]);
|
|
34
|
+
});
|
|
35
|
+
it('escapes the key as a JSON Pointer', () => {
|
|
36
|
+
expect(buildMetadataFieldPatch('a/b~c', 'x', undefined)).to.deep.equal([
|
|
37
|
+
{ op: 'add', path: '/a~1b~0c', value: 'x' },
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=metadata-patch.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-patch.test.js","sourceRoot":"","sources":["../../test/metadata-patch.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { buildMetadataFieldPatch } from '../src/metadata-patch';\n\ndescribe('buildMetadataFieldPatch', () => {\n it('replaces an existing field', () => {\n expect(buildMetadataFieldPatch('title', 'New', 'Old')).to.deep.equal([\n { op: 'replace', path: '/title', value: 'New' },\n ]);\n });\n\n it('adds a field that is absent', () => {\n expect(buildMetadataFieldPatch('volume', ['2'], undefined)).to.deep.equal([\n { op: 'add', path: '/volume', value: '2' },\n ]);\n });\n\n it('writes a single-entry list as a bare string', () => {\n expect(\n buildMetadataFieldPatch('subject', ['one'], ['one', 'two']),\n ).to.deep.equal([{ op: 'replace', path: '/subject', value: 'one' }]);\n });\n\n it('keeps multiple values as a list', () => {\n expect(buildMetadataFieldPatch('subject', ['a', 'b'], 'a')).to.deep.equal([\n { op: 'replace', path: '/subject', value: ['a', 'b'] },\n ]);\n });\n\n it('removes a field set to nothing', () => {\n expect(buildMetadataFieldPatch('notes', [], 'old')).to.deep.equal([\n { op: 'remove', path: '/notes' },\n ]);\n expect(buildMetadataFieldPatch('notes', '', ['a', 'b'])).to.deep.equal([\n { op: 'remove', path: '/notes' },\n ]);\n });\n\n it('returns an empty patch when nothing changes', () => {\n expect(buildMetadataFieldPatch('title', ['Same'], 'Same')).to.deep.equal(\n [],\n );\n expect(buildMetadataFieldPatch('title', 'Same', ['Same'])).to.deep.equal(\n [],\n );\n expect(buildMetadataFieldPatch('notes', [], undefined)).to.deep.equal([]);\n });\n\n it('escapes the key as a JSON Pointer', () => {\n expect(buildMetadataFieldPatch('a/b~c', 'x', undefined)).to.deep.equal([\n { op: 'add', path: '/a~1b~0c', value: 'x' },\n ]);\n });\n});\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { expect } from '@open-wc/testing';
|
|
3
|
+
import { MetadataService } from '../src/metadata-service';
|
|
4
|
+
import { MetadataServiceError, MetadataServiceErrorType, } from '../src/metadata-service-error';
|
|
5
|
+
class WritableMockBackend {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.current = {};
|
|
8
|
+
this.writes = [];
|
|
9
|
+
}
|
|
10
|
+
async fetchMetadata(_identifier, keypath) {
|
|
11
|
+
if (this.readError)
|
|
12
|
+
return { error: this.readError };
|
|
13
|
+
if (keypath !== 'metadata')
|
|
14
|
+
return { success: {} };
|
|
15
|
+
return { success: { result: this.current } };
|
|
16
|
+
}
|
|
17
|
+
async writeMetadata(identifier, target, patch) {
|
|
18
|
+
if (this.writeError)
|
|
19
|
+
return { error: this.writeError };
|
|
20
|
+
this.writes.push({ identifier, target, patch });
|
|
21
|
+
return { success: { success: true, task_id: 42 } };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
describe('MetadataService writes', () => {
|
|
25
|
+
describe('patchMetadata', () => {
|
|
26
|
+
it('sends the patch to the metadata target by default', async () => {
|
|
27
|
+
const backend = new WritableMockBackend();
|
|
28
|
+
const service = new MetadataService(backend);
|
|
29
|
+
const patch = [{ op: 'remove', path: '/notes' }];
|
|
30
|
+
const result = await service.patchMetadata('foo', patch);
|
|
31
|
+
expect(result.success).to.deep.equal({ changed: true, taskId: 42 });
|
|
32
|
+
expect(backend.writes).to.deep.equal([
|
|
33
|
+
{ identifier: 'foo', target: 'metadata', patch },
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
it('passes a backend error through', async () => {
|
|
37
|
+
var _a;
|
|
38
|
+
const backend = new WritableMockBackend();
|
|
39
|
+
backend.writeError = new MetadataServiceError(MetadataServiceErrorType.writeError, 'nope');
|
|
40
|
+
const service = new MetadataService(backend);
|
|
41
|
+
const result = await service.patchMetadata('foo', []);
|
|
42
|
+
expect((_a = result.error) === null || _a === void 0 ? void 0 : _a.message).to.equal('nope');
|
|
43
|
+
});
|
|
44
|
+
it('reports writeNotSupported for a backend without writes', async () => {
|
|
45
|
+
var _a;
|
|
46
|
+
const readOnlyBackend = {
|
|
47
|
+
fetchMetadata: async () => ({ success: {} }),
|
|
48
|
+
};
|
|
49
|
+
const service = new MetadataService(readOnlyBackend);
|
|
50
|
+
const result = await service.patchMetadata('foo', []);
|
|
51
|
+
expect((_a = result.error) === null || _a === void 0 ? void 0 : _a.type).to.equal(MetadataServiceErrorType.writeNotSupported);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('updateMetadataField', () => {
|
|
55
|
+
it('replaces a field MDAPI has', async () => {
|
|
56
|
+
const backend = new WritableMockBackend();
|
|
57
|
+
backend.current = { title: 'Old' };
|
|
58
|
+
const service = new MetadataService(backend);
|
|
59
|
+
const result = await service.updateMetadataField('foo', 'title', 'New');
|
|
60
|
+
expect(result.success).to.deep.equal({ changed: true, taskId: 42 });
|
|
61
|
+
expect(backend.writes[0].patch).to.deep.equal([
|
|
62
|
+
{ op: 'replace', path: '/title', value: 'New' },
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
it('adds a field MDAPI does not have', async () => {
|
|
66
|
+
const backend = new WritableMockBackend();
|
|
67
|
+
backend.current = { title: 'T' };
|
|
68
|
+
const service = new MetadataService(backend);
|
|
69
|
+
await service.updateMetadataField('foo', 'volume', ['2']);
|
|
70
|
+
expect(backend.writes[0].patch).to.deep.equal([
|
|
71
|
+
{ op: 'add', path: '/volume', value: '2' },
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
it('skips the write when MDAPI already has the value', async () => {
|
|
75
|
+
const backend = new WritableMockBackend();
|
|
76
|
+
backend.current = { subject: ['a', 'b'] };
|
|
77
|
+
const service = new MetadataService(backend);
|
|
78
|
+
const result = await service.updateMetadataField('foo', 'subject', [
|
|
79
|
+
'a',
|
|
80
|
+
'b',
|
|
81
|
+
]);
|
|
82
|
+
expect(result.success).to.deep.equal({ changed: false });
|
|
83
|
+
expect(backend.writes).to.be.empty;
|
|
84
|
+
});
|
|
85
|
+
it('returns the read error without writing', async () => {
|
|
86
|
+
var _a;
|
|
87
|
+
const backend = new WritableMockBackend();
|
|
88
|
+
backend.readError = new MetadataServiceError(MetadataServiceErrorType.searchEngineError, "Couldn't locate item 'foo'");
|
|
89
|
+
const service = new MetadataService(backend);
|
|
90
|
+
const result = await service.updateMetadataField('foo', 'title', 'New');
|
|
91
|
+
expect((_a = result.error) === null || _a === void 0 ? void 0 : _a.message).to.equal("Couldn't locate item 'foo'");
|
|
92
|
+
expect(backend.writes).to.be.empty;
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=metadata-service-write.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-service-write.test.js","sourceRoot":"","sources":["../../test/metadata-service-write.test.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { expect } from '@open-wc/testing';\n\nimport { Result } from '@internetarchive/result-type';\nimport { MetadataBackendInterface } from '../src/backend/metadata-backend-interface';\nimport { MetadataService } from '../src/metadata-service';\nimport {\n MetadataServiceError,\n MetadataServiceErrorType,\n} from '../src/metadata-service-error';\nimport type { MetadataPatchOperation } from '../src/metadata-patch';\n\nclass WritableMockBackend implements MetadataBackendInterface {\n current: Record<string, unknown> = {};\n\n readError?: MetadataServiceError;\n\n writeError?: MetadataServiceError;\n\n writes: { identifier: string; target: string; patch: any[] }[] = [];\n\n async fetchMetadata(\n _identifier: string,\n keypath?: string,\n ): Promise<Result<any, MetadataServiceError>> {\n if (this.readError) return { error: this.readError };\n if (keypath !== 'metadata') return { success: {} };\n return { success: { result: this.current } };\n }\n\n async writeMetadata(\n identifier: string,\n target: string,\n patch: MetadataPatchOperation[],\n ): Promise<Result<any, MetadataServiceError>> {\n if (this.writeError) return { error: this.writeError };\n this.writes.push({ identifier, target, patch });\n return { success: { success: true, task_id: 42 } };\n }\n}\n\ndescribe('MetadataService writes', () => {\n describe('patchMetadata', () => {\n it('sends the patch to the metadata target by default', async () => {\n const backend = new WritableMockBackend();\n const service = new MetadataService(backend);\n const patch = [{ op: 'remove' as const, path: '/notes' }];\n\n const result = await service.patchMetadata('foo', patch);\n\n expect(result.success).to.deep.equal({ changed: true, taskId: 42 });\n expect(backend.writes).to.deep.equal([\n { identifier: 'foo', target: 'metadata', patch },\n ]);\n });\n\n it('passes a backend error through', async () => {\n const backend = new WritableMockBackend();\n backend.writeError = new MetadataServiceError(\n MetadataServiceErrorType.writeError,\n 'nope',\n );\n const service = new MetadataService(backend);\n\n const result = await service.patchMetadata('foo', []);\n\n expect(result.error?.message).to.equal('nope');\n });\n\n it('reports writeNotSupported for a backend without writes', async () => {\n const readOnlyBackend: MetadataBackendInterface = {\n fetchMetadata: async () => ({ success: {} }),\n };\n const service = new MetadataService(readOnlyBackend);\n\n const result = await service.patchMetadata('foo', []);\n\n expect(result.error?.type).to.equal(\n MetadataServiceErrorType.writeNotSupported,\n );\n });\n });\n\n describe('updateMetadataField', () => {\n it('replaces a field MDAPI has', async () => {\n const backend = new WritableMockBackend();\n backend.current = { title: 'Old' };\n const service = new MetadataService(backend);\n\n const result = await service.updateMetadataField('foo', 'title', 'New');\n\n expect(result.success).to.deep.equal({ changed: true, taskId: 42 });\n expect(backend.writes[0].patch).to.deep.equal([\n { op: 'replace', path: '/title', value: 'New' },\n ]);\n });\n\n it('adds a field MDAPI does not have', async () => {\n const backend = new WritableMockBackend();\n backend.current = { title: 'T' };\n const service = new MetadataService(backend);\n\n await service.updateMetadataField('foo', 'volume', ['2']);\n\n expect(backend.writes[0].patch).to.deep.equal([\n { op: 'add', path: '/volume', value: '2' },\n ]);\n });\n\n it('skips the write when MDAPI already has the value', async () => {\n const backend = new WritableMockBackend();\n backend.current = { subject: ['a', 'b'] };\n const service = new MetadataService(backend);\n\n const result = await service.updateMetadataField('foo', 'subject', [\n 'a',\n 'b',\n ]);\n\n expect(result.success).to.deep.equal({ changed: false });\n expect(backend.writes).to.be.empty;\n });\n\n it('returns the read error without writing', async () => {\n const backend = new WritableMockBackend();\n backend.readError = new MetadataServiceError(\n MetadataServiceErrorType.searchEngineError,\n \"Couldn't locate item 'foo'\",\n );\n const service = new MetadataService(backend);\n\n const result = await service.updateMetadataField('foo', 'title', 'New');\n\n expect(result.error?.message).to.equal(\"Couldn't locate item 'foo'\");\n expect(backend.writes).to.be.empty;\n });\n });\n});\n"]}
|
|
@@ -56,6 +56,17 @@ export class MockResponseGenerator {
|
|
|
56
56
|
server: 'ia800201.us.archive.org',
|
|
57
57
|
uniq: 162444403,
|
|
58
58
|
workable_servers: ['ia800201.us.archive.org', 'ia600201.us.archive.org'],
|
|
59
|
+
alternate_locations: {
|
|
60
|
+
servers: [
|
|
61
|
+
{ server: 'ia800201.us.archive.org', dir: '/27/items/foo' },
|
|
62
|
+
{ server: 'ia600201.us.archive.org', dir: '/27/items/foo' },
|
|
63
|
+
],
|
|
64
|
+
workable: [{ server: 'ia800201.us.archive.org', dir: '/27/items/foo' }],
|
|
65
|
+
},
|
|
66
|
+
clips: { '60|120': [1, 2, 3] },
|
|
67
|
+
plays: {},
|
|
68
|
+
simplelists: {},
|
|
69
|
+
solo: false,
|
|
59
70
|
};
|
|
60
71
|
}
|
|
61
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-response-generator.js","sourceRoot":"","sources":["../../test/mock-response-generator.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,OAAO,qBAAqB;IAChC,4BAA4B,CAAC,UAAkB;QAC7C,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,EAAE,EAAE,yBAAyB;YAC7B,EAAE,EAAE,yBAAyB;YAC7B,GAAG,EAAE,wCAAwC;YAC7C,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,YAAY;oBACpB,MAAM,EAAE,WAAW;oBACnB,QAAQ,EAAE,SAAS;oBACnB,GAAG,EAAE,kCAAkC;oBACvC,KAAK,EAAE,YAAY;oBACnB,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,UAAU;oBACjB,IAAI,EAAE,0CAA0C;iBACjD;gBACD;oBACE,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,YAAY;oBACpB,MAAM,EAAE,WAAW;oBACnB,QAAQ,EAAE,SAAS;oBACnB,GAAG,EAAE,kCAAkC;oBACvC,KAAK,EAAE,YAAY;oBACnB,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,UAAU;oBACjB,IAAI,EAAE,0CAA0C;iBACjD;aACF;YACD,WAAW,EAAE,CAAC;YACd,iBAAiB,EAAE,UAAU;YAC7B,SAAS,EAAE,QAAQ;YACnB,QAAQ,EAAE;gBACR,OAAO,EACL,gFAAgF;gBAClF,SAAS,EAAE,QAAQ;gBACnB,KAAK,EAAE,wCAAwC;gBAC/C,WAAW,EAAE,wCAAwC;gBACrD,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,8DAA8D;gBACtE,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,eAAe;gBACvB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,YAAY;gBACrB,UAAU,EAAE,qBAAqB;gBACjC,UAAU;gBACV,QAAQ,EAAE,oBAAoB;gBAC9B,SAAS,EAAE,qBAAqB;gBAChC,UAAU,EAAE,qBAAqB;gBACjC,UAAU,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC;gBACrC,eAAe,EAAE,YAAY;aAC9B;YACD,MAAM,EAAE,yBAAyB;YACjC,IAAI,EAAE,SAAS;YACf,gBAAgB,EAAE,CAAC,yBAAyB,EAAE,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"mock-response-generator.js","sourceRoot":"","sources":["../../test/mock-response-generator.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,OAAO,qBAAqB;IAChC,4BAA4B,CAAC,UAAkB;QAC7C,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,EAAE,EAAE,yBAAyB;YAC7B,EAAE,EAAE,yBAAyB;YAC7B,GAAG,EAAE,wCAAwC;YAC7C,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,YAAY;oBACpB,MAAM,EAAE,WAAW;oBACnB,QAAQ,EAAE,SAAS;oBACnB,GAAG,EAAE,kCAAkC;oBACvC,KAAK,EAAE,YAAY;oBACnB,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,UAAU;oBACjB,IAAI,EAAE,0CAA0C;iBACjD;gBACD;oBACE,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,YAAY;oBACpB,MAAM,EAAE,WAAW;oBACnB,QAAQ,EAAE,SAAS;oBACnB,GAAG,EAAE,kCAAkC;oBACvC,KAAK,EAAE,YAAY;oBACnB,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,UAAU;oBACjB,IAAI,EAAE,0CAA0C;iBACjD;aACF;YACD,WAAW,EAAE,CAAC;YACd,iBAAiB,EAAE,UAAU;YAC7B,SAAS,EAAE,QAAQ;YACnB,QAAQ,EAAE;gBACR,OAAO,EACL,gFAAgF;gBAClF,SAAS,EAAE,QAAQ;gBACnB,KAAK,EAAE,wCAAwC;gBAC/C,WAAW,EAAE,wCAAwC;gBACrD,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,8DAA8D;gBACtE,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,eAAe;gBACvB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,YAAY;gBACrB,UAAU,EAAE,qBAAqB;gBACjC,UAAU;gBACV,QAAQ,EAAE,oBAAoB;gBAC9B,SAAS,EAAE,qBAAqB;gBAChC,UAAU,EAAE,qBAAqB;gBACjC,UAAU,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC;gBACrC,eAAe,EAAE,YAAY;aAC9B;YACD,MAAM,EAAE,yBAAyB;YACjC,IAAI,EAAE,SAAS;YACf,gBAAgB,EAAE,CAAC,yBAAyB,EAAE,yBAAyB,CAAC;YACxE,mBAAmB,EAAE;gBACnB,OAAO,EAAE;oBACP,EAAE,MAAM,EAAE,yBAAyB,EAAE,GAAG,EAAE,eAAe,EAAE;oBAC3D,EAAE,MAAM,EAAE,yBAAyB,EAAE,GAAG,EAAE,eAAe,EAAE;iBAC5D;gBACD,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC;aACxE;YACD,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;YAC9B,KAAK,EAAE,EAAE;YACT,WAAW,EAAE,EAAE;YACf,IAAI,EAAE,KAAK;SACZ,CAAC;IACJ,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class MockResponseGenerator {\n generateMockMetadataResponse(identifier: string): any {\n return {\n created: 1586477049,\n d1: 'ia600201.us.archive.org',\n d2: 'ia800201.us.archive.org',\n dir: '/27/items/rss-383924main_TWAN_09_04_09',\n files: [\n {\n name: 'foo.jpg',\n source: 'derivative',\n format: 'Thumbnail',\n original: 'foo.mp4',\n md5: '48067b43a547d3e90cb433a04ba84d5d',\n mtime: '1256675427',\n size: '1135',\n crc32: '40870038',\n sha1: '6445c2a6f51c8314c1872ec1f7daf33c5cfabd06',\n },\n {\n name: 'bar.jpg',\n source: 'derivative',\n format: 'Thumbnail',\n original: 'bar.mp4',\n md5: '5bf69912d7b796fe309cde32e61230bc',\n mtime: '1256675429',\n size: '6329',\n crc32: 'ba5f361a',\n sha1: 'cddab0e2daab29978e5efdeff735240a44aa7c80',\n },\n ],\n files_count: 2,\n item_last_updated: 1463797130,\n item_size: 99872691,\n metadata: {\n feed_id:\n '/0/rss_feeds/NASACast_Video/nasacast_video:sts128_landing/NASAcast_vodcast.rss',\n mediatype: 'movies',\n title: \"NASA TV's This Week @NASA, September 4\",\n description: \"NASA TV's This Week @NASA, September 4\",\n creator: 'NASA',\n source: 'http://www.nasa.gov/multimedia/podcasting/twan_09_04_09.html',\n date: '9/4/2009',\n year: '2009',\n rights: 'Public Domain',\n language: 'en-us',\n updater: 'BonnieReal',\n updatedate: '2009-10-27 20:23:01',\n identifier,\n uploader: 'bonnie@archive.org',\n addeddate: '2009-10-27 20:25:55',\n publicdate: '2009-10-27 20:41:27',\n collection: ['nasa', 'nasacastvideo'],\n backup_location: 'ia903604_7',\n },\n server: 'ia800201.us.archive.org',\n uniq: 162444403,\n workable_servers: ['ia800201.us.archive.org', 'ia600201.us.archive.org'],\n alternate_locations: {\n servers: [\n { server: 'ia800201.us.archive.org', dir: '/27/items/foo' },\n { server: 'ia600201.us.archive.org', dir: '/27/items/foo' },\n ],\n workable: [{ server: 'ia800201.us.archive.org', dir: '/27/items/foo' }],\n },\n clips: { '60|120': [1, 2, 3] },\n plays: {},\n simplelists: {},\n solo: false,\n };\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { MetadataResponse } from '../../src/responses/metadata-response';
|
|
3
|
+
describe('MetadataResponse', () => {
|
|
4
|
+
it('parses alternate_locations into servers and workable lists', async () => {
|
|
5
|
+
var _a, _b, _c, _d;
|
|
6
|
+
const response = new MetadataResponse({
|
|
7
|
+
metadata: { identifier: 'foo' },
|
|
8
|
+
alternate_locations: {
|
|
9
|
+
servers: [
|
|
10
|
+
{ server: 'ia801234.us.archive.org', dir: '/12/items/foo' },
|
|
11
|
+
{ server: 'ia601234.us.archive.org', dir: '/12/items/foo' },
|
|
12
|
+
],
|
|
13
|
+
workable: [{ server: 'ia801234.us.archive.org', dir: '/12/items/foo' }],
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
expect((_a = response.alternate_locations) === null || _a === void 0 ? void 0 : _a.servers).to.have.lengthOf(2);
|
|
17
|
+
expect((_b = response.alternate_locations) === null || _b === void 0 ? void 0 : _b.servers[0].server).to.equal('ia801234.us.archive.org');
|
|
18
|
+
expect((_c = response.alternate_locations) === null || _c === void 0 ? void 0 : _c.servers[0].dir).to.equal('/12/items/foo');
|
|
19
|
+
expect((_d = response.alternate_locations) === null || _d === void 0 ? void 0 : _d.workable).to.have.lengthOf(1);
|
|
20
|
+
});
|
|
21
|
+
it('exposes the clips, plays, and simplelists maps', async () => {
|
|
22
|
+
const response = new MetadataResponse({
|
|
23
|
+
metadata: { identifier: 'foo' },
|
|
24
|
+
clips: { '60|120': [1, 2, 3] },
|
|
25
|
+
plays: { total: 5 },
|
|
26
|
+
simplelists: {},
|
|
27
|
+
});
|
|
28
|
+
expect(response.clips).to.deep.equal({ '60|120': [1, 2, 3] });
|
|
29
|
+
expect(response.plays).to.deep.equal({ total: 5 });
|
|
30
|
+
expect(response.simplelists).to.deep.equal({});
|
|
31
|
+
});
|
|
32
|
+
it('exposes the solo flag', async () => {
|
|
33
|
+
const response = new MetadataResponse({
|
|
34
|
+
metadata: { identifier: 'foo' },
|
|
35
|
+
solo: true,
|
|
36
|
+
});
|
|
37
|
+
expect(response.solo).to.be.true;
|
|
38
|
+
});
|
|
39
|
+
it('leaves the top-level fields undefined when absent', async () => {
|
|
40
|
+
const response = new MetadataResponse({ metadata: { identifier: 'foo' } });
|
|
41
|
+
expect(response.alternate_locations).to.be.undefined;
|
|
42
|
+
expect(response.clips).to.be.undefined;
|
|
43
|
+
expect(response.plays).to.be.undefined;
|
|
44
|
+
expect(response.simplelists).to.be.undefined;
|
|
45
|
+
expect(response.solo).to.be.undefined;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=metadata-response.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-response.test.js","sourceRoot":"","sources":["../../../test/responses/metadata-response.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\n\nimport { MetadataResponse } from '../../src/responses/metadata-response';\n\ndescribe('MetadataResponse', () => {\n it('parses alternate_locations into servers and workable lists', async () => {\n const response = new MetadataResponse({\n metadata: { identifier: 'foo' },\n alternate_locations: {\n servers: [\n { server: 'ia801234.us.archive.org', dir: '/12/items/foo' },\n { server: 'ia601234.us.archive.org', dir: '/12/items/foo' },\n ],\n workable: [{ server: 'ia801234.us.archive.org', dir: '/12/items/foo' }],\n },\n });\n expect(response.alternate_locations?.servers).to.have.lengthOf(2);\n expect(response.alternate_locations?.servers[0].server).to.equal(\n 'ia801234.us.archive.org',\n );\n expect(response.alternate_locations?.servers[0].dir).to.equal(\n '/12/items/foo',\n );\n expect(response.alternate_locations?.workable).to.have.lengthOf(1);\n });\n\n it('exposes the clips, plays, and simplelists maps', async () => {\n const response = new MetadataResponse({\n metadata: { identifier: 'foo' },\n clips: { '60|120': [1, 2, 3] },\n plays: { total: 5 },\n simplelists: {},\n });\n expect(response.clips).to.deep.equal({ '60|120': [1, 2, 3] });\n expect(response.plays).to.deep.equal({ total: 5 });\n expect(response.simplelists).to.deep.equal({});\n });\n\n it('exposes the solo flag', async () => {\n const response = new MetadataResponse({\n metadata: { identifier: 'foo' },\n solo: true,\n });\n expect(response.solo).to.be.true;\n });\n\n it('leaves the top-level fields undefined when absent', async () => {\n const response = new MetadataResponse({ metadata: { identifier: 'foo' } });\n expect(response.alternate_locations).to.be.undefined;\n expect(response.clips).to.be.undefined;\n expect(response.plays).to.be.undefined;\n expect(response.simplelists).to.be.undefined;\n expect(response.solo).to.be.undefined;\n });\n});\n"]}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { MetadataResponse } from './src/responses/metadata-response';
|
|
2
|
+
export type {
|
|
3
|
+
AlternateLocation,
|
|
4
|
+
AlternateLocations,
|
|
5
|
+
} from './src/responses/metadata-response';
|
|
2
6
|
|
|
3
7
|
export {
|
|
4
8
|
File,
|
|
@@ -9,4 +13,12 @@ export {
|
|
|
9
13
|
export { DefaultMetadataBackend } from './src/backend/default-metadata-backend';
|
|
10
14
|
export { MetadataService } from './src/metadata-service';
|
|
11
15
|
export { MetadataServiceError } from './src/metadata-service-error';
|
|
12
|
-
export {
|
|
16
|
+
export {
|
|
17
|
+
MetadataServiceInterface,
|
|
18
|
+
MetadataWriteResult,
|
|
19
|
+
} from './src/metadata-service-interface';
|
|
20
|
+
export { buildMetadataFieldPatch } from './src/metadata-patch';
|
|
21
|
+
export type {
|
|
22
|
+
MetadataFieldValue,
|
|
23
|
+
MetadataPatchOperation,
|
|
24
|
+
} from './src/metadata-patch';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetarchive/metadata-service",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.2.0-beta.0",
|
|
4
4
|
"description": "A service for fetching metadata about items in the Internet Archive",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@internetarchive/field-parsers": "^0.1.4",
|
|
30
|
-
"@internetarchive/iaux-item-metadata": "^1.
|
|
30
|
+
"@internetarchive/iaux-item-metadata": "^1.3.0",
|
|
31
31
|
"@internetarchive/result-type": "^0.0.1",
|
|
32
32
|
"typescript-memoize": "^1.1.1"
|
|
33
33
|
},
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
MetadataServiceErrorType,
|
|
6
6
|
} from '../metadata-service-error';
|
|
7
7
|
import { MetadataBackendInterface } from './metadata-backend-interface';
|
|
8
|
+
import type { MetadataPatchOperation } from '../metadata-patch';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* The DefaultSearchBackend performs a `window.fetch` request to archive.org
|
|
@@ -56,6 +57,52 @@ export class DefaultMetadataBackend implements MetadataBackendInterface {
|
|
|
56
57
|
return this.fetchUrl(url);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @inheritdoc
|
|
62
|
+
*
|
|
63
|
+
* Makes a single attempt: a write that timed out may still have queued its
|
|
64
|
+
* task, and retrying would queue another.
|
|
65
|
+
*/
|
|
66
|
+
async writeMetadata(
|
|
67
|
+
identifier: string,
|
|
68
|
+
target: string,
|
|
69
|
+
patch: MetadataPatchOperation[],
|
|
70
|
+
): Promise<Result<any, MetadataServiceError>> {
|
|
71
|
+
const url = `https://${this.baseUrl}/metadata/${identifier}`;
|
|
72
|
+
const body = new URLSearchParams({
|
|
73
|
+
'-target': target,
|
|
74
|
+
'-patch': JSON.stringify(patch),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = await this.fetchUrl(url, {
|
|
78
|
+
requestOptions: {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
credentials: this.includeCredentials ? 'include' : 'same-origin',
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
83
|
+
},
|
|
84
|
+
body,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// MDAPI reports a rejected write as `{ success: false, error }`
|
|
89
|
+
if (result.error?.type === MetadataServiceErrorType.searchEngineError) {
|
|
90
|
+
return this.getErrorResult(
|
|
91
|
+
MetadataServiceErrorType.writeError,
|
|
92
|
+
result.error.message,
|
|
93
|
+
result.error.details,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (result.success && result.success.success !== true) {
|
|
97
|
+
return this.getErrorResult(
|
|
98
|
+
MetadataServiceErrorType.writeError,
|
|
99
|
+
'Metadata write was not accepted',
|
|
100
|
+
result.success,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
59
106
|
/**
|
|
60
107
|
* Fires a request to the URL (with this backend's options applied) and
|
|
61
108
|
* asynchronously returns a Result object containing either the raw response
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type { Result } from '@internetarchive/result-type';
|
|
3
3
|
import { MetadataServiceError } from '../metadata-service-error';
|
|
4
|
+
import type { MetadataPatchOperation } from '../metadata-patch';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* An interface to provide the network layer to the `MetadataService`.
|
|
@@ -22,4 +23,22 @@ export interface MetadataBackendInterface {
|
|
|
22
23
|
identifier: string,
|
|
23
24
|
keypath?: string,
|
|
24
25
|
): Promise<Result<any, MetadataServiceError>>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Apply a JSON Patch to one of an item's targets (e.g. `metadata`) through
|
|
29
|
+
* the MDAPI write endpoint, as the logged-in user. Resolves with MDAPI's raw
|
|
30
|
+
* response (`{ success, task_id, log }`) on success.
|
|
31
|
+
*
|
|
32
|
+
* Optional so backends written before writes existed keep compiling; the
|
|
33
|
+
* service reports `writeNotSupported` for a backend without it.
|
|
34
|
+
*
|
|
35
|
+
* @param identifier
|
|
36
|
+
* @param target
|
|
37
|
+
* @param patch
|
|
38
|
+
*/
|
|
39
|
+
writeMetadata?(
|
|
40
|
+
identifier: string,
|
|
41
|
+
target: string,
|
|
42
|
+
patch: MetadataPatchOperation[],
|
|
43
|
+
): Promise<Result<any, MetadataServiceError>>;
|
|
25
44
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** A metadata field value as MDAPI stores it: one string, or a list of them */
|
|
2
|
+
export type MetadataFieldValue = string | string[];
|
|
3
|
+
|
|
4
|
+
/** One JSON Patch (RFC 6902) operation, as MDAPI's write endpoint accepts */
|
|
5
|
+
export type MetadataPatchOperation =
|
|
6
|
+
| { op: 'add' | 'replace'; path: string; value: unknown }
|
|
7
|
+
| { op: 'remove'; path: string };
|
|
8
|
+
|
|
9
|
+
/** Escapes a metadata key for use as a JSON Pointer segment (RFC 6901) */
|
|
10
|
+
function pointerFor(field: string): string {
|
|
11
|
+
return `/${field.replace(/~/g, '~0').replace(/\//g, '~1')}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** MDAPI's storage shape: a one-entry list collapses to a bare string */
|
|
15
|
+
function normalize(
|
|
16
|
+
value: MetadataFieldValue | undefined,
|
|
17
|
+
): MetadataFieldValue | undefined {
|
|
18
|
+
return Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isEmpty(value: MetadataFieldValue | undefined): boolean {
|
|
22
|
+
if (value == null) return true;
|
|
23
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
24
|
+
return value === '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds the JSON Patch that sets one metadata field to `value`, given the
|
|
29
|
+
* field's current value.
|
|
30
|
+
*
|
|
31
|
+
* A single-entry list is written as a bare string, which is how MDAPI stores
|
|
32
|
+
* a one-value field, and an empty string or list removes the field. Returns
|
|
33
|
+
* an empty patch when nothing changes, since MDAPI rejects a write that
|
|
34
|
+
* doesn't modify the item.
|
|
35
|
+
*/
|
|
36
|
+
export function buildMetadataFieldPatch(
|
|
37
|
+
field: string,
|
|
38
|
+
value: MetadataFieldValue,
|
|
39
|
+
current: MetadataFieldValue | undefined,
|
|
40
|
+
): MetadataPatchOperation[] {
|
|
41
|
+
const path = pointerFor(field);
|
|
42
|
+
const next = normalize(value);
|
|
43
|
+
const existing = normalize(current);
|
|
44
|
+
|
|
45
|
+
if (next == null || isEmpty(next)) {
|
|
46
|
+
return isEmpty(existing) ? [] : [{ op: 'remove', path }];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (JSON.stringify(next) === JSON.stringify(existing)) return [];
|
|
50
|
+
|
|
51
|
+
const op = isEmpty(existing) ? 'add' : 'replace';
|
|
52
|
+
return [{ op, path, value: next }];
|
|
53
|
+
}
|
|
@@ -3,6 +3,8 @@ export enum MetadataServiceErrorType {
|
|
|
3
3
|
itemNotFound = 'MetadataService.ItemNotFound',
|
|
4
4
|
decodingError = 'MetadataService.DecodingError',
|
|
5
5
|
searchEngineError = 'MetadataService.SearchEngineError',
|
|
6
|
+
writeError = 'MetadataService.WriteError',
|
|
7
|
+
writeNotSupported = 'MetadataService.WriteNotSupported',
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export class MetadataServiceError extends Error {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { Result } from '@internetarchive/result-type';
|
|
2
2
|
import type { MetadataServiceError } from './metadata-service-error';
|
|
3
3
|
import type { MetadataResponse } from './responses/metadata-response';
|
|
4
|
+
import type {
|
|
5
|
+
MetadataFieldValue,
|
|
6
|
+
MetadataPatchOperation,
|
|
7
|
+
} from './metadata-patch';
|
|
8
|
+
|
|
9
|
+
export type MetadataWriteResult = {
|
|
10
|
+
/** Whether a write was sent. False when the field already had the value. */
|
|
11
|
+
changed: boolean;
|
|
12
|
+
/** The catalog task that will apply the change to the item */
|
|
13
|
+
taskId?: number;
|
|
14
|
+
};
|
|
4
15
|
|
|
5
16
|
export interface MetadataServiceInterface {
|
|
6
17
|
/**
|
|
@@ -48,4 +59,49 @@ export interface MetadataServiceInterface {
|
|
|
48
59
|
identifier: string,
|
|
49
60
|
keypath: string,
|
|
50
61
|
): Promise<Result<T, MetadataServiceError>>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Apply a JSON Patch to an item's metadata as the logged-in user.
|
|
65
|
+
*
|
|
66
|
+
* MDAPI applies the change through a queued catalog task. MDAPI reads
|
|
67
|
+
* replay pending tasks' patches ("lookahead"), so they show the change right
|
|
68
|
+
* away, but caches built on MDAPI (search, page services) can lag it. MDAPI
|
|
69
|
+
* rejects a patch that doesn't change the item.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* await metadataService.patchMetadata('goody', [
|
|
73
|
+
* { op: 'replace', path: '/title', value: 'Goody Two-Shoes' },
|
|
74
|
+
* ]);
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param identifier
|
|
78
|
+
* @param patch JSON Patch operations against the item's metadata
|
|
79
|
+
* @param target MDAPI write target, `metadata` by default
|
|
80
|
+
*/
|
|
81
|
+
patchMetadata(
|
|
82
|
+
identifier: string,
|
|
83
|
+
patch: MetadataPatchOperation[],
|
|
84
|
+
target?: string,
|
|
85
|
+
): Promise<Result<MetadataWriteResult, MetadataServiceError>>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Set one metadata field on an item as the logged-in user. An empty string
|
|
89
|
+
* or list removes the field.
|
|
90
|
+
*
|
|
91
|
+
* Reads the item's current metadata from MDAPI first and patches against
|
|
92
|
+
* that, so the right add / replace / remove goes out even when the caller's
|
|
93
|
+
* copy is stale. That read includes this user's still-queued writes through
|
|
94
|
+
* MDAPI's lookahead, except for tasks lookahead skips (e.g. ones held for an
|
|
95
|
+
* admin). Resolves with `changed: false`, without writing, when the field
|
|
96
|
+
* already has the value.
|
|
97
|
+
*
|
|
98
|
+
* @param identifier
|
|
99
|
+
* @param field The metadata key, e.g. `title`
|
|
100
|
+
* @param value
|
|
101
|
+
*/
|
|
102
|
+
updateMetadataField(
|
|
103
|
+
identifier: string,
|
|
104
|
+
field: string,
|
|
105
|
+
value: MetadataFieldValue,
|
|
106
|
+
): Promise<Result<MetadataWriteResult, MetadataServiceError>>;
|
|
51
107
|
}
|