Skip to content

Commit

Permalink
Merge pull request #40 from cosmicjs/staging
Browse files Browse the repository at this point in the history
v1.1.0: Props graph syntax, media data
  • Loading branch information
tonyspiro authored Sep 19, 2024
2 parents 2f2b47f + 0042953 commit 637e4c5
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-comics-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cosmicjs/sdk': minor
---

Adds: props graph syntax to Objects fetching, media data fetching option
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
node_modules
dist

test.*
test.*
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ src
.prettierrc.js
.nvmrc
tsconfig.json

test.*
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cosmicjs/sdk",
"version": "1.0.12",
"version": "1.1.0",
"description": "The official client module for Cosmic. This module helps you easily add dynamic content to your website or application using the Cosmic headless CMS.",
"keywords": [
"headlesscms",
Expand Down
48 changes: 41 additions & 7 deletions src/clients/bucket/lib/methodChaining.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
export default class MethodChaining {
endpoint: string = '';

opts: any;

constructor(endpoint: string) {
this.endpoint = endpoint;
}

props(props: string | Array<string>) {
let propStr = props;
if (Array.isArray(propStr)) {
propStr = propStr
.filter((prop) => typeof prop === 'string')
let propStr: string;

if (typeof props === 'string') {
propStr =
props.startsWith('{') && props.endsWith('}')
? this.parseGraphQLProps(props.slice(1, -1))
: props;
} else if (Array.isArray(props)) {
propStr = props
.filter((prop): prop is string => typeof prop === 'string')
.map((prop) => prop.trim())
.filter((prop) => !!prop)
.toString();
.filter(Boolean)
.join(',');
} else {
throw new Error('Invalid props type');
}
this.endpoint += `&props=${propStr}`;
this.endpoint += `&props=${encodeURIComponent(propStr)}`;
return this;
}

private parseGraphQLProps(propsString: string): string {
const lines = propsString
.split('\n')
.map((line) => line.trim())
.filter(Boolean);
const result: string[] = [];
const currentPath: string[] = [];

for (const line of lines) {
if (line.includes('{')) {
const [key] = line.split('{');
if (key !== undefined) {
currentPath.push(key.trim());
}
} else if (line === '}') {
currentPath.pop();
} else {
result.push([...currentPath, line].join('.'));
}
}

return result.join(',');
}

sort(sort: string) {
this.endpoint += `&sort=${sort}`;
return this;
Expand Down
3 changes: 0 additions & 3 deletions src/clients/bucket/media/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ export const mediaChainMethods = (
if (params.metadata) {
data.append('metadata', JSON.stringify(params.metadata));
}
if (params.alt_text) {
data.append('alt_text', params.alt_text);
}
if (params.trigger_webhook) {
data.append('trigger_webhook', params.trigger_webhook.toString());
}
Expand Down
4 changes: 2 additions & 2 deletions src/clients/bucket/objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const objectsChainMethods = (
const endpoint = `${apiConfig.apiUrl}/buckets/${
bucketConfig.bucketSlug
}/objects?read_key=${bucketConfig.readKey}${encodedQueryParam(query)}`;
return new FindChaining(endpoint);
return new FindChaining(endpoint, bucketConfig);
},

findOne<T extends Record<string, unknown>>(query: NonEmptyObject<T>) {
Expand All @@ -26,7 +26,7 @@ export const objectsChainMethods = (
}/objects?read_key=${bucketConfig.readKey}&limit=1${encodedQueryParam(
query
)}`;
return new FindOneChaining(endpoint);
return new FindOneChaining(endpoint, bucketConfig);
},

async insertOne(data: GenericObject) {
Expand Down
30 changes: 30 additions & 0 deletions src/clients/bucket/objects/lib/chaining.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
import MethodChaining from '../../lib/methodChaining';

/**
* Options for fetching object data.
* @property {Object} media - Options for media objects.
* @property {string} media.props - Comma-separated list of additional properties to fetch for media objects.
* @typedef {Object} MediaType
* @property {string} all - All media properties.
* @property {string} id - The unique identifier of the media object.
* @property {string} name - The name of the media file.
* @property {string} original_name - The original name of the media file.
* @property {number} size - The size of the media file in bytes.
* @property {string} type - The MIME type of the media file.
* @property {string} bucket - The bucket identifier.
* @property {string} created_at - The creation date of the media object.
* @property {string} folder - The folder where the media is stored.
* @property {string} url - The URL of the media file.
* @property {string} imgix_url - The Imgix URL of the media file.
* @property {string} alt_text - The alternative text for the media.
*/
type OptionsType = {
media: {
props: string;
};
};
export default class Chaining extends MethodChaining {
depth(depth: number) {
this.endpoint += `&depth=${depth}`;
Expand All @@ -15,4 +38,11 @@ export default class Chaining extends MethodChaining {
this.endpoint += `&after=${after}`;
return this;
}

options(options: OptionsType) {
if (options) {
this.opts = options;
}
return this;
}
}
24 changes: 21 additions & 3 deletions src/clients/bucket/objects/lib/find.chaining.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { PromiseFnType } from '../../../../types/promise.types';
import { promiserTryCatchWrapper } from '../../../../utils/request.promiser';
import Chaining from './chaining';
import { addFullMediaData } from '../../../../utils/addFullMedia';
import { BucketConfig } from '../../../../types/config.types';
import { createBucketClient } from '../..';

export default class FindChaining extends Chaining {
private bucketConfig: BucketConfig;

constructor(endpoint: string, bucketConfig: BucketConfig) {
super(endpoint);
this.bucketConfig = bucketConfig;
}

limit(limit: number) {
this.endpoint += `&limit=${limit}`;
return this;
Expand All @@ -12,8 +22,16 @@ export default class FindChaining extends Chaining {
onFulfilled?: PromiseFnType<FulfilledResult>,
onRejected?: PromiseFnType<RejectedResult>
) {
await promiserTryCatchWrapper(this.endpoint, onRejected, (res) =>
onFulfilled?.(res)
);
await promiserTryCatchWrapper(this.endpoint, onRejected, async (res) => {
// eslint-disable-next-line no-underscore-dangle
if (this.opts && this.opts.media && res.objects) {
res.objects = await addFullMediaData(
res.objects,
createBucketClient(this.bucketConfig),
this.opts.media.props
);
}
onFulfilled?.(res);
});
}
}
25 changes: 21 additions & 4 deletions src/clients/bucket/objects/lib/findOne.chaining.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { PromiseFnType } from '../../../../types/promise.types';
import { promiserTryCatchWrapper } from '../../../../utils/request.promiser';
import Chaining from './chaining';
import { addFullMediaData } from '../../../../utils/addFullMedia';
import { BucketConfig } from '../../../../types/config.types';
import { createBucketClient } from '../..';

export default class FindOneChaining extends Chaining {
private bucketConfig: BucketConfig;

constructor(endpoint: string, bucketConfig: BucketConfig) {
super(endpoint);
this.bucketConfig = bucketConfig;
}

async then<FulfilledResult = any, RejectedResult = never>(
onFulfilled?: PromiseFnType<FulfilledResult>,
onRejected?: PromiseFnType<RejectedResult>
) {
await promiserTryCatchWrapper(this.endpoint, onRejected, (res) => {
onFulfilled?.({
object: res.objects && res.objects.length ? res.objects[0] : null,
});
await promiserTryCatchWrapper(this.endpoint, onRejected, async (res) => {
let object = res.objects && res.objects.length ? res.objects[0] : null;
if (this.opts && this.opts.media && object) {
object = await addFullMediaData(
object,
createBucketClient(this.bucketConfig),
this.opts.media.props
);
}

onFulfilled?.({ object });
});
}
}
1 change: 0 additions & 1 deletion src/types/media.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { GenericObject } from './generic.types';
export type InsertMediaType = {
media: any;
folder?: string;
alt_text?: string;
metadata?: GenericObject;
trigger_webhook?: boolean;
};
77 changes: 77 additions & 0 deletions src/utils/addFullMedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const fetchMediaData = async (
cosmic: any,
filenames: string[],
props: string
) => {
const query = {
name: { $in: filenames },
};
const { media } = await cosmic.media
.find(query)
.props(!props || props === 'all' ? '' : `name,url,imgix_url,${props}`);
return media;
};

const extractMediaFiles = (obj: any): string[] => {
const mediaFiles: string[] = [];
JSON.stringify(obj, (_, value) => {
if (value && typeof value === 'object') {
const url = value.imgix_url || value.url;
if (url) {
mediaFiles.push(url.split('/').pop().split('?')[0]);
}
}
return value;
});
return [...new Set(mediaFiles)];
};

const mapMediaDataToResponse = (
response: any,
mediaData: any[],
props: string
) => {
const mediaMap = new Map(mediaData.map((item) => [item.name, item]));

const addFullMedia = (obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => {
if (obj[key] && typeof obj[key] === 'object') {
const url = obj[key].imgix_url || obj[key].url;
if (url) {
const filename = url.split('/').pop().split('?')[0];
if (mediaMap.has(filename)) {
// eslint-disable-next-line no-param-reassign
if (!props.includes('name')) {
delete mediaMap.get(filename).name;
}
const newObj = { ...mediaMap.get(filename) };
Object.assign(obj[key], newObj);
}
}
addFullMedia(obj[key]);
}
});
}
};

addFullMedia(response);
};

const addFullMediaData = async (response: any, cosmic: any, props: string) => {
const processItem = async (item: any) => {
const mediaFiles = extractMediaFiles(item);
if (mediaFiles.length > 0) {
const mediaData = await fetchMediaData(cosmic, mediaFiles, props);
mapMediaDataToResponse(item, mediaData, props);
}
return item;
};

if (Array.isArray(response)) {
return Promise.all(response.map((item) => processItem(item)));
}
return processItem(response);
};

export { addFullMediaData };

0 comments on commit 637e4c5

Please sign in to comment.