Skip to content

Commit

Permalink
API is now promises only
Browse files Browse the repository at this point in the history
- Minor type improvements
- Extremely minor extention to test coverage
  • Loading branch information
drzax committed Oct 4, 2024
1 parent 9f4189b commit 32ffc0e
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 103 deletions.
55 changes: 15 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,22 @@ import { fetchOne, search } from '@abcnews/terminus-fetch';

// By default, we assume you want an Article document from Core Media so you can pass a CMID:

fetchOne(10736062, (err, doc) => {
if (!err) {
console.log(doc);
// > { id: 10736062, docType: "Article", contentSource: "coremedia", ... }
}
});
fetchOne(10736062).then(console.log);
// > { id: 10736062, docType: "Article", contentSource: "coremedia", ... }

// ...or you can pass an options object to override the defaults (see API below):

fetchOne({ id: 10734902, type: 'video' }, (err, doc) => {
if (!err) {
console.log(doc);
// > {id: 10734902, docType: "Video", contentSource: "coremedia", ... }
}
});

// You can use promises instead of callbacks:

fetchOne({ id: 123860, type: 'show', source: 'iview' })
.then(doc => {
console.log(doc);
// > { id: 123860, docType: "show", contentSource: "iview", ... }
})
.catch(err => console.error(err));
fetchOne({ id: 10734902, type: 'video' }).then(console.log);
// > {id: 10734902, docType: "Video", contentSource: "coremedia", ... }

// Searching is also supported:

search({ limit: 3, doctype: 'image' }), (err, docs) => {
if (!err) {
console.log(docs);
// > [
// { id: 11405582, docType: "Image", contentSource: "coremedia", ... },
// { id: 11404970, docType: "Image", contentSource: "coremedia", ... },
// { id: 11405258, docType: "Image", contentSource: "coremedia", ... }
// ]
}
});
search({ limit: 3, doctype: 'image' })).then(console.log);
// > [
// { id: 11405582, docType: "Image", contentSource: "coremedia", ... },
// { id: 11404970, docType: "Image", contentSource: "coremedia", ... },
// { id: 11405258, docType: "Image", contentSource: "coremedia", ... }
// ]

// ...for all sources...:

Expand All @@ -68,7 +47,7 @@ search({ limit: 1, source: 'mapi', service: 'triplej'})
.catch(err => console.error(err));
```

If your project's JS is currently executing in a page on `*.aus.aunty.abc.net.au`, requests will be made to Preview Terminus (`https://api-preview.terminus.abc-prod.net.au/api/v2/{teasable}content`), otherwise they'll be made to Live Terminus (`https://api.abc.net.au/terminus/api/v2/{teasable}content`).
If your project's JS is currently executing in a page on a preview domain, requests will be made to Preview Terminus, otherwise they'll be made to Live Terminus.

If you want to direct a single request to Live Terminus, regardless of the current execution domain, pass `force: "live"` as an option.

Expand All @@ -91,9 +70,8 @@ declare function fetchOne(
id?: string | number;
force?: 'preview' | 'live';
isTeasable?: string;
},
done?: (err?: ProgressEvent | Error, doc?: Object) => void
): void | Promise<Object>;
}
): Promise<TerminusDocument>;
```

If the `done` callback is omitted then the return value will be a Promise.
Expand All @@ -115,9 +93,8 @@ declare function search(
source?: string;
force?: "preview" | "live";
...searchParams: Object;
},
done?: (err?: ProgressEvent | Error, doc?: Object) => void
): void | Promise<Object>;
}
): Promise<TerminusDocument[]>;
```

...where your `searchParams` are additional properties on your `options` object, to query the API.
Expand All @@ -131,8 +108,6 @@ For example, if you wanted the last 20 images added to Core Media, your `searchP
}
```

If the `done` callback is omitted then the return value will be a Promise.

#### Default options

These are the same as `fetchOne`, only split across two options arguments.
Expand Down
3 changes: 3 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ describe('getImages', () => {
test('Standard image proxy with defaultRatio', () => {
expect(getImages(v2_standard_proxy_with_default_ratio.input).defaultRatio).toEqual('4x3');
});
test('Bad terminus doc', () => {
expect(() => getImages({})).toThrow();
});
});
});
89 changes: 26 additions & 63 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ interface SearchOptions extends APIOptions {
source?: string;
[x: string]: unknown;
}
interface TerminusDocument {
export interface TerminusDocument {
_links?: Record<string, unknown>;
_embedded?: {
[key: string]: TerminusDocument[];
};
}
type Callback<E, T> = (err?: E, result?: T) => void;
type Done<T> = Callback<ProgressEvent | Error, T>;

// This built JS asset _will_be_ rewritten on-the-fly, so we need to obscure the origin somewhat
const GENIUNE_MEDIA_ENDPOINT_PATTERN = new RegExp(['http', '://', 'mpegmedia', '.abc.net.au'].join(''), 'g');
Expand Down Expand Up @@ -61,61 +59,38 @@ function getEndpoint(force?: TIERS): string {
: TERMINUS_LIVE_ENDPOINT;
}

function fetchOne(fetchOneOptions: FetchOneOptionsOrDocumentID): Promise<TerminusDocument>;
function fetchOne(fetchOneOptions: FetchOneOptionsOrDocumentID, done: Done<TerminusDocument>): void;
function fetchOne(fetchOneOptions: FetchOneOptionsOrDocumentID, done?: Done<TerminusDocument>): unknown {
return asyncTask(
new Promise<TerminusDocument>((resolve, reject) => {
const { source, type, id, isTeasable, force, version } = {
...DEFAULT_API_OPTIONS,
...DEFAULT_DOCUMENT_OPTIONS,
...ensureIsDocumentOptions(fetchOneOptions)
};
async function fetchOne(fetchOneOptions: FetchOneOptionsOrDocumentID): Promise<TerminusDocument> {
const { source, type, id, isTeasable, force, version } = {
...DEFAULT_API_OPTIONS,
...DEFAULT_DOCUMENT_OPTIONS,
...ensureIsDocumentOptions(fetchOneOptions)
};

if (isDocumentIDInvalid(id as DocumentID)) {
return reject(new Error(`Invalid ID: ${id}`));
}
if (isDocumentIDInvalid(id as DocumentID)) {
throw new Error(`Invalid ID: ${id}`);
}

request(
`${getBaseUrl({ force, version })}/${
isTeasable ? 'teasable' : ''
}content/${source}/${type}/${id}?apikey=${API_KEY}`,
resolve,
reject
);
}),
done
const res = await fetch(
`${getBaseUrl({ force, version })}/${isTeasable ? 'teasable' : ''}content/${source}/${type}/${id}?apikey=${API_KEY}`
);
const responseText = await res.text();
return parse(responseText);
}

function search(searchOptions: SearchOptions): Promise<TerminusDocument[]>;
function search(searchOptions: SearchOptions, done: Done<TerminusDocument[]>): void;
function search(searchOptions?: SearchOptions, done?: Done<TerminusDocument[]>): unknown {
return asyncTask(
new Promise<TerminusDocument[]>((resolve, reject) => {
const { force, source, version, ...searchParams } = {
...DEFAULT_SEARCH_OPTIONS,
...(searchOptions || ({} as SearchOptions))
};
const searchParamsKeys = Object.keys(searchParams);
async function search(searchOptions: SearchOptions): Promise<TerminusDocument[]> {
const { force, source, version, ...searchParams } = {
...DEFAULT_SEARCH_OPTIONS,
...(searchOptions || ({} as SearchOptions))
};
const searchParamsKeys = Object.keys(searchParams);

request(
`${getBaseUrl({ force, version })}/search/${source}?${searchParamsKeys
.map(key => `${key}=${searchParams[key]}`)
.join('&')}${searchParamsKeys.length ? '&' : ''}apikey=${API_KEY}`,
(response: TerminusDocument) => resolve(flattenEmbeddedProps(response._embedded || {})),
reject
);
}),
done
const res = await fetch(
`${getBaseUrl({ force, version })}/search/${source}?${searchParamsKeys
.map(key => `${key}=${searchParams[key]}`)
.join('&')}${searchParamsKeys.length ? '&' : ''}apikey=${API_KEY}`
);
}

// Enable easy support for both promise and callback interfaces
function asyncTask<E, T>(promise: Promise<T>, callback?: Callback<E, T>) {
return callback
? promise.then(result => setTimeout(callback, 0, null, result)).catch(err => setTimeout(callback, 0, err))
: promise;
const _embedded = await res.json();
return flattenEmbeddedProps(_embedded);
}

function ensureIsDocumentOptions(options: DocumentOptionsOrDocumentID): DocumentOptions {
Expand All @@ -126,18 +101,6 @@ function isDocumentIDInvalid(documentID: DocumentID): boolean {
return documentID != +documentID || !String(documentID).length || String(documentID).indexOf('.') > -1;
}

function request(uri: string, resolve: (data: TerminusDocument) => unknown, reject: (err: ProgressEvent) => unknown) {
const xhr = new XMLHttpRequest();
const errorHandler = (event: ProgressEvent) => reject(event);

xhr.onload = event => (xhr.status !== 200 ? reject(event) : resolve(parse(xhr.responseText)));
xhr.onabort = errorHandler;
xhr.onerror = errorHandler;
xhr.open('GET', uri, true);
xhr.responseType = 'text';
xhr.send();
}

function parse(responseText: string): TerminusDocument {
// Terminus is not returning proxied asset URLs (yet)
return JSON.parse(responseText.replace(GENIUNE_MEDIA_ENDPOINT_PATTERN, PROXIED_MEDIA_ENDPOINT));
Expand Down

0 comments on commit 32ffc0e

Please sign in to comment.