New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@ts-ghost/core-api

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ts-ghost/core-api - npm Package Compare versions

Comparing version 0.0.7 to 0.1.0

36

./dist/index.js

@@ -262,3 +262,3 @@ "use strict";

getIncludes() {
return this._includeFields;
return this._params?.include || [];
}

@@ -286,4 +286,2 @@ _buildUrlParams() {

_urlBrowseParams() {
if (this._params === void 0)
return {};
let urlBrowseParams = {};

@@ -391,3 +389,11 @@ if (this._params.browseParams === void 0)

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -425,3 +431,3 @@ return result;

getIncludes() {
return this._includeFields;
return this._params?.include || [];
}

@@ -494,3 +500,11 @@ _buildUrlParams() {

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -636,3 +650,11 @@ return result;

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -639,0 +661,0 @@ return result;

12

dist/index.d.ts

@@ -443,2 +443,5 @@ import { z, ZodRawShape } from 'zod';

type OrderObjectKeyMask<Obj> = {
[k in keyof Obj]?: "ASC" | "DESC";
};
/**

@@ -471,3 +474,3 @@ * QueryBuilder class

*/
browse<P extends {
browse<Fields extends z.objectKeyMask<OutputShape>, Include extends z.objectKeyMask<IncludeShape>, Order extends OrderObjectKeyMask<Shape>, P extends {
order?: string;

@@ -477,4 +480,7 @@ limit?: number | string;

filter?: string;
}, Fields extends z.objectKeyMask<OutputShape>, Include extends z.objectKeyMask<IncludeShape>>(options?: {
input?: BrowseParams<P, Shape>;
_unstable_order?: Order;
}>(options?: {
input?: BrowseParams<P, Shape> & {
_unstable_order?: z.noUnrecognized<Order, Shape>;
};
output?: {

@@ -481,0 +487,0 @@ fields?: z.noUnrecognized<Fields, OutputShape>;

@@ -262,3 +262,3 @@ "use strict";

getIncludes() {
return this._includeFields;
return this._params?.include || [];
}

@@ -286,4 +286,2 @@ _buildUrlParams() {

_urlBrowseParams() {
if (this._params === void 0)
return {};
let urlBrowseParams = {};

@@ -391,3 +389,11 @@ if (this._params.browseParams === void 0)

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -425,3 +431,3 @@ return result;

getIncludes() {
return this._includeFields;
return this._params?.include || [];
}

@@ -494,3 +500,11 @@ _buildUrlParams() {

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -636,3 +650,11 @@ return result;

} catch (e) {
console.log("error", e);
return {
status: "error",
errors: [
{
type: "FetchError",
message: e.toString()
}
]
};
}

@@ -639,0 +661,0 @@ return result;

@@ -10,5 +10,5 @@ {

"type": "git",
"url": "https://github.com/PhilDL/ts-ghost/apps/ghost-blog-buster"
"url": "https://github.com/PhilDL/ts-ghost/tree/main/packages/ts-ghost-core-api"
},
"version": "0.0.7",
"version": "0.1.0",
"main": "./dist/index.js",

@@ -29,3 +29,4 @@ "module": "./dist/index.mjs",

"vite-tsconfig-paths": "^4.0.5",
"vitest": "^0.29.1"
"vitest": "^0.29.1",
"vitest-fetch-mock": "^0.2.2"
},

@@ -46,2 +47,3 @@ "dependencies": {

"test:coverage": "vitest --coverage",
"test-ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'",
"lint": "eslint ./src --fix",

@@ -48,0 +50,0 @@ "typecheck": "tsc --project ./tsconfig.json --noEmit"

@@ -22,3 +22,3 @@ <br/>

`@ts-ghost/core-api` contains the core building blocks for the `@ts-ghost/content-api` package. It contains the Type-safe logic of Query Builder and Fetchers.
`@ts-ghost/core-api` contains the core building blocks for the `@ts-ghost/content-api` package. It contains the Type-safe logic of Query Builder and Fetchers. Unless you are building a new package for `@ts-ghost` you should not need to use this package directly.

@@ -31,12 +31,325 @@ ## Install

## Query Builders
## QueryBuilder
### Global instantiation
A QueryBuilder is a class that helps you build a query based on a a combinations of ZodSchema. This QueryBuilder exposes 2 methods `read` to fetch a single record and `browse` to fetch multiple records. `read` and `browse` gives you back the appropriate `Fetcher` instance that will handle the actual request to the API with the correct parameters.
Dependeing on the Fetcher you want to use, the instantiation may be a bit different
`QueryBuilder` will handle type-safety of the query parameters and will return the appropriate type based on the `ZodSchema` you pass to it. eg: if you pass the `fields` parameters that "select" fields you want to be present on the response instead of the whole object, then the output schema of the QueryBuilder will change. And then passed to the fetcher to validate data.
### `BrowseQueryBuilder`
### Instantiation
```ts
import type { ContentAPICredentials } from "../schemas";
import { QueryBuilder } from "./query-builder";
import { z } from "zod";
const api: ContentAPICredentials = {
url: "https://ghost.org",
key: "7d2d15d7338526d43c2fadc47c",
version: "v5.0",
endpoint: "posts",
};
const simplifiedSchema = z.object({
title: z.string(),
slug: z.string(),
count: z.number().optional(),
});
// the "include" schema is used to validate the "include" parameters of the API call
// it is specific to the Ghost API endpoint from resource to resource.
// The format is always { 'name_of_the_field': true }
const simplifiedIncludeSchema = z.object({
count: z.literal(true).optional(),
});
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
```
The output schema is really not necessary, since it will be modified along the way after the params are added to the query. At instantiation it will most likely be the same as the original schema so the API may change and we may remove that key.
### Building Queries
After instantiation you can use the `QueryBuilder` to build your queries with 2 available methods.
The `browse` and `read` methods accept a config object with 2 properties: `input` and an `output`. These params mimic the way Ghost API Content is built but with the power of Zod and TypeScript they are type-safe here.
```typescript
import { QueryBuilder, type ContentAPICredentials } from "@ts-ghost/core-api";
import { z } from "zod";
const api: ContentAPICredentials = { url: "https://ghost.org", key: "7d2d15d7338526d43c2fadc47c", version: "v5.0", endpoint: "posts",};
const simplifiedSchema = z.object({
title: z.string(),
slug: z.string(),
count: z.number().optional(),
});
const simplifiedIncludeSchema = z.object({
count: z.literal(true).optional(),
});
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
let query = qb.browse({
input: {
limit: 5,
order: "title DESC"
// ^? the text here will throw a TypeScript lint error if you use unknown field.
// In that case `title` is correctly defined in the `simplifiedSchema
},
output: {
include: {
count: true,
// ^? Available inputs here come from the `simplifiedIncludeSchema`
},
},
} as const);
```
- `input` will accept browse parameters like `page`, `limit`, `order`, `filter`. And read parameters are `id` or `slug`.
- `output` is the same for both methods and let you specify `fields` to output (to not have the full object) and some Schema specific `include`. For example getting the posts including their Authors.
*Ghost Content API doesn't work well when you mix `fields` and `include` output, so in most case you shouldn't*
## `input`
### `.browse` inputs
Input are totally optionals on the `browse` method but they let you filter and order your search.
This is an example containing all the available keys in the `input` object
```typescript
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
let query = qb.browse({
input: {
page: 1,
limit: 5,
filter: "title:typescript+slug:-test",
order: "title DESC"
}
} as const); // notice the `as const` necessary to have type hints of the order and filter.
```
These browse params are then parsed through a `Zod` Schema that will validate all the fields.
#### Type-hint with `as const`
You should use `as const` for your input if you are playing with `filter` and `order` so TypeScript can analyse the content of the string statically and TypeCheck it.
- `page:number` The current page requested
- `limit:number` Between 0 and 15 (limitation of the Ghost API)
- `filter:string` Contains the filter with [Ghost API `filter` syntax](https://ghost.org/docs/content-api/#filtering).
- `order:string` Contains the name of the field and the order `ASC` or `DESC`.
For the `order` and `filter` if you use fields that are not present on the schema (for example `name` on a `Post`) then the QueryBuilder will throw an Error with message containing the unknown field.
### `.read` inputs
Read is meant to be used to fetch 1 object only by `id` or `slug`.
```typescript
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
let query = qb.read({
input: {
id: "edHks74hdKqhs34izzahd45"
}
});
// or
let query = qb.read({
input: {
slug: "typescript-is-awesome-in-2025"
}
});
```
You can submit **both** `id` and `slug`, but the fetcher will then chose the `id` in priority if present to make the final URL query to the Ghost API.
## `output`
Output is the same for both `browse` and `read` methods and gives you 2 keys to play with
### `fields`
The `fields` key lets you change the output of the result to have only your selected fields, it works by giving the key and the value `true` to the field you want to keep. Under the hood it will use the `zod.pick` method to pick only the fields you want.
```typescript
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
let result = await qb.read({
input: {
slug: "typescript-is-cool"
},
output: {
fields: {
slug: true,
title: true
// ^? available fields come form the `simplifiedSchema` passed in the constructor
}
}
} as const).fetch();
if (result.status === 'success') {
const post = result.data;
// ^? type {"slug":string; "title": string}
}
```
The **output schema** will be modified to only have the fields you selected and TypeScript will pick up on that to warn you if you access non-existing fields.
### `include`
The `include` key lets you include some additionnal data that the Ghost API doesn't give you by default. This `include` key is specific to each endpoint and is defined in the `Schema` of the endpoint. You will have to let TypeScript guide you to know what you can include.
```typescript
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
let result = await qb.read({
input: {
slug: "phildl"
},
output: {
include: {
"count": true,
},
},
} as const).fetch();
```
## Fetchers
If the parsing went okay, the `read` and `browse` methods from the `QueryBuilder` will return the associated `Fetcher`.
- `BrowseFetcher` for the `browse` method
- `ReadFetcher` for the `read` method
- `BasicFetcher` is a special case when you don't need a QueryBuilder at all and want to fetch directly.
These Fetchers are instantiated in a similar way as the QueryBuilder with a `config` containing the same schemas. But also a set of params
necessary to build the URL to the Ghost API.
```typescript
import { BrowseFetcher } from "@ts-ghost/core-api";
// Example of instantiating a Fetcher, even though you will probably not do it
const browseFetcher = new BrowseFetcher(
{
schema: simplifiedSchema,
output: simplifiedSchema,
include: simplifiedIncludeSchema,
},
{
browseParams: {
limit: 1,
},
},
api
);
```
These fetchers have a `fetch` method that will return a discriminated union of 2 types:
```typescript
const qb = new QueryBuilder(
{ schema: simplifiedSchema, output: simplifiedSchema, include: simplifiedIncludeSchema },
api
);
const readFetcher = qb.read({input: {slug: "typescript-is-cool"}});
let result = await readFetcher.fetch();
if (result.status === 'success') {
const post = result.data;
// ^? type {"slug":string; "title": string}
} else {
// errors array of objects
console.log(result.errors.map(e => e.message).join('\n'))
}
```
### Read Fetcher
After using `.read` query, you will get a `ReadFetcher` with an `async fetch` method giving you a discriminated union of 2 types:
```typescript
// example for the read query (the data is an object)
const result: {
status: "success";
data: z.infer<typeof simplifiedSchema>; // parsed by the Zod Schema and modified by the fields selected
} | {
status: "error";
errors: {
message: string;
type: string;
}[];
}
```
### Browse Fetcher
After using `.read` query, you will get a `BrowseFetcher` with 2 methods:
- `async fetch`
- `async paginate`
#### Browse `.fetch()`
That result is a discriminated union of 2 types:
```typescript
// example for the browse query (the data is an array of objects)
const result: {
status: "success";
data: z.infer<typeof simplifiedSchema>[];
meta: {
pagination: {
pages: number;
limit: number;
page: number;
total: number;
prev: number | null;
next: number | null;
};
};
} | {
status: "error";
errors: {
message: string;
type: string;
}[];
}
```
#### Browse `.paginate()`
```typescript
const result: {
status: "success";
data: z.infer<typeof simplifiedSchema>[];
meta: {
pagination: {
pages: number;
limit: number;
page: number;
total: number;
prev: number | null;
next: number | null;
};
};
next: BrowseFetcher | undefined; // the next page fetcher if it is defined
} | {
status: "error";
errors: {
message: string;
type: string;
}[];
next: undefined; // the next page fetcher is undefined here
}
```
Here you can use the `next` property to get the next page fetcher if it is defined.
## Roadmap

@@ -43,0 +356,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc