Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.1.0: Props graph syntax, media data #40

Merged
merged 24 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9d98cdf
add: props graph syntax
tonyspiro Sep 2, 2024
2918c43
edit: remove alt_text (another PR)
tonyspiro Sep 2, 2024
0d3d89a
add: full_media_data option
tonyspiro Sep 3, 2024
6d251a5
edit: strip query string
tonyspiro Sep 3, 2024
0421d28
add: explicit option type
tonyspiro Sep 3, 2024
ec622b3
edit: declare media props
tonyspiro Sep 4, 2024
0964d56
edit: declare media props
tonyspiro Sep 4, 2024
0b2fd89
fix: query params
tonyspiro Sep 4, 2024
8aac471
merge
tonyspiro Sep 4, 2024
a21857e
edit: url or imgix_url to get filename
tonyspiro Sep 7, 2024
346bebf
Merge branch 'tony/full-media' into merge-test
tonyspiro Sep 7, 2024
726b38f
edit: url or imgix_url to get filename
tonyspiro Sep 7, 2024
404ea34
Merge branch 'main' into tony/props-graph-syntax
tonyspiro Sep 10, 2024
381cccb
merge alt-text, props
tonyspiro Sep 11, 2024
962d198
Merge branch 'tony/full-media' of https://github.com/cosmicjs/cosmic-…
tonyspiro Sep 11, 2024
f2cd49f
Merge branch 'main' of https://github.com/cosmicjs/cosmic-sdk-js into…
tonyspiro Sep 11, 2024
79571fe
Merge branch 'tony/props-graph-syntax' of https://github.com/cosmicjs…
tonyspiro Sep 11, 2024
1158e45
fix: url issue
tonyspiro Sep 11, 2024
c888bd9
Merge branch 'tony/full-media' into tony/canary
tonyspiro Sep 11, 2024
e3bc452
Merge branch 'canary' of https://github.com/cosmicjs/cosmic-sdk-js in…
tonyspiro Sep 12, 2024
d195c46
add: npm ignore
tonyspiro Sep 19, 2024
0d7ae13
edit: pkg update
tonyspiro Sep 19, 2024
ebbca38
edit: pkg update
tonyspiro Sep 19, 2024
0042953
add: release changeset
tonyspiro Sep 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };
Loading