Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
A highly-modular (typescript-friendly)-framework agnostic library for serializing data to the JSON:API specification
A highly-modular (typescript-friendly)-framework agnostic library for serializing data to the JSON:API specification
The documentation has everything that is covered here and more.
You can install ts-japi in your project's directory as usual:
npm install ts-japi
There are fives classes that are used to serialize data (only one of which is necessarily required).
Serializer
with
SerializerOptions
Relator
with
RelatorOptions
Linker
with
LinkerOptions
Metaizer
Paginator
ErrorSerializer
with
ErrorSerializerOptions
Cache
with
CacheOptions
You can check the documentation for a deeper insight into the usage.
You can check the examples and the
test folders to see some examples
(such as the ones below). You can check
this example to
see almost every option of
Serializer
exhausted.
The Serializer
class is the
only class required for basic serialization.
The following example constructs the most basic
Serializer
: (Note the await
)
import { Serializer } from '../src';
import { User } from '../test/models';
import { getJSON } from '../test/utils/get-json';
const UserSerializer = new Serializer('users');
(async () => {
const user = new User('sample_user_id');
console.log('Output:', getJSON(await UserSerializer.serialize(user)));
// Output: {
// jsonapi: { version: '1.0' },
// data: {
// type: 'users',
// id: 'sample_user_id',
// attributes: {
// createdAt: '2020-05-20T15:44:37.650Z',
// articles: [],
// comments: []
// }
// }
// }
})();
The Linker
class is used to
generate a normalized document link. Its methods are
not meant to be called. See the FAQ for reasons.
The following example constructs a
Linker
for User
s and Article
s:
import { Linker } from '../src';
import { User, Article } from '../test/models';
import { getJSON } from '../test/utils/get-json';
// The last argument should almost always be an array or a single object type.
// The reason for this is the potential for linking several articles.
const UserArticleLinker = new Linker((user: User, articles: Article | Article[]) => {
return Array.isArray(articles)
? `https://www.example.com/users/${user.id}/articles/`
: `https://www.example.com/users/${user.id}/articles/${articles.id}`;
});
// ! The rest of this example is just to illustrate internal behavior.
(async () => {
const user = new User('sample_user_id');
const article = new Article('same_article_id', user);
console.log('Output:', getJSON(UserArticleLinker.link(user, article)));
// Output: https://www.example.com/users/sample_user_id/articles/same_article_id
})();
The Paginator
class is used to
generate pagination links. Its methods are not
meant to be called.
The following example constructs a
Paginator
:
import { Paginator } from '../src';
import { User, Article } from '../test/models';
import { getJSON } from '../test/utils/get-json';
const ArticlePaginator = new Paginator((articles: Article | Article[]) => {
if (Array.isArray(articles)) {
const nextPage = Number(articles[0].id) + 1;
const prevPage = Number(articles[articles.length - 1].id) - 1;
return {
first: `https://www.example.com/articles/0`,
last: `https://www.example.com/articles/10`,
next: nextPage <= 10 ? `https://www.example.com/articles/${nextPage}` : null,
prev: prevPage >= 0 ? `https://www.example.com/articles/${prevPage}` : null,
};
}
return;
});
// ! The rest of this example is just to illustrate internal behavior.
(async () => {
const user = new User('sample_user_id');
const article = new Article('same_article_id', user);
console.log('Output:', getJSON(ArticlePaginator.paginate([article])));
// Output: {
// first: 'https://www.example.com/articles/0',
// last: 'https://www.example.com/articles/10',
// prev: null,
// next: null
// }
})();
The Relator
class is used to
generate top-level included data as well as
resource-level relationships.
Its methods are not meant to be called.
Relator
s may also take optional
Linker
s (using the
linker
option)
to define relationship links
and
related resource links.
The following example constructs a
Relator
for User
s and
Article
s:
import { Serializer, Relator } from '../src';
import { User, Article } from '../test/models';
import { getJSON } from '../test/utils/get-json';
const ArticleSerializer = new Serializer<Article>('articles');
const UserArticleRelator = new Relator<User, Article>(
async (user) => user.getArticles(),
ArticleSerializer
);
// ! The rest of this example is just to illustrate some internal behavior.
(async () => {
const user = new User('sample_user_id');
const article = new Article('same_article_id', user);
User.save(user);
Article.save(article);
console.log('Output:', getJSON(await UserArticleRelator.getRelationship(user)));
// Output: { data: [ { type: 'articles', id: 'same_article_id' } ] }
})();
The Metaizer
class is used to
construct generate metadata given some dependencies. There are several locations
Metaizer
can be used:
ErrorSerializerOptions.metaizers
RelatorOptions.metaizer
SerializerOptions.metaizers
LinkerOptions.metaizer
Like Linker
, its methods are not
meant to be called.
The following example constructs a
Metaizer
:
import { User, Article } from '../test/models';
import { Metaizer } from '../src';
import { getJSON } from '../test/utils/get-json';
// The last argument should almost always be an array or a single object type.
// The reason for this is the potential for metaizing several articles.
const UserArticleMetaizer = new Metaizer((user: User, articles: Article | Article[]) => {
return Array.isArray(articles)
? { user_created: user.createdAt, article_created: articles.map((a) => a.createdAt) }
: { user_created: user.createdAt, article_created: articles.createdAt };
});
// ! The rest of this example is just to illustrate internal behavior.
(async () => {
const user = new User('sample_user_id');
const article = new Article('same_article_id', user);
console.log('Output:', getJSON(UserArticleMetaizer.metaize(user, article)));
// Output: {
// user_created: '2020-05-20T15:39:43.277Z',
// article_created: '2020-05-20T15:39:43.277Z'
// }
})();
The ErrorSerializer
class
is used to serialize any object considered an error (the
attributes
option allows you to choose what attributes to use during serialization). Alternatively
(recommended), you can construct custom errors by extending the
JapiError
class and use those
for all server-to-client errors.
The error serializer test includes an example of the alternative solution.
The following example constructs the most basic
ErrorSerializer
: (Note
the lack of await
)
import { ErrorSerializer } from '../src';
import { getJSON } from '../test/utils/get-json';
const PrimitiveErrorSerializer = new ErrorSerializer();
(async () => {
const error = new Error('badness');
console.log('Output:', getJSON(PrimitiveErrorSerializer.serialize(error)));
// Output: {
// errors: [ { code: 'Error', detail: 'badness' } ],
// jsonapi: { version: '1.0' }
// }
})();
The Cache
class can be placed in a
Serializer
's
cache
option.
Alternatively, setting that option to true
will provide a default
Cache
.
The default Cache
uses the basic
Object.is
function to determine if input data are the same. If you want to adjust this, instantiate a new
Cache
with a
resolver
.
We stress the following: Given that there are many clients readily built to consume JSON:API endpoints (see here), we do not provide deserialization. In particular, since unmarshalling data is strongly related to the code it will be used in (e.g. React), tighter integration is recommended over an unnecessary abstraction.
There are several model classes used inside TS:JAPI such as Resource
and Relationships
. These
models are used for normalization as well as traversing a JSON:API document. If you plan to fork
this repo, you can extend these models and reimplement them to create your own custom (non-standard,
extended) serializer.
Why not just allow optional functions that return the internal
Link
Class (or just a URIstring
)?
The Link
class is defined to be as general as possible in case of changes in the specification. In
particular, the implementation of metadata and the types in our library rely on the generality of
the Link
class. Relying on user arguments will generate a lot of overhead for both us and users
whenever the specs change.
Why does the
Meta
class exist if it is essentially just a plain object?
In case the specification is updated to change the meta objects in some functional way.
Due to compound documents, it is possible
to recurse through related resources via their
resource linkages and obtain
included resources beyond primary data relations.
This is should be done with caution (see
SerializerOptions.depth
and
this example)
To get started in developing this library, run yarn install
, yarn build
and yarn test
(in this
precise order) to assure everything is in working order.
This project is maintained by the author, however contributions are welcome and appreciated. You can find TS:JAPI on GitHub: https://github.com/mathematic-inc/ts-japi
Feel free to submit an issue, but please do not submit pull requests unless it is to fix some issue. For more information, read the contribution guide.
Copyright © 2020 mathematic-inc.
Licensed under Apache 2.0.
FAQs
A highly-modular (typescript-friendly)-framework agnostic library for serializing data to the JSON:API specification
The npm package ts-japi receives a total of 5,907 weekly downloads. As such, ts-japi popularity was classified as popular.
We found that ts-japi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.