Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
2c065dc
Extracted shared API framework to separate package
daniellockyer Aug 11, 2022
b10cf90
Updated API framework debug and test names
daniellockyer Aug 11, 2022
b98b4c2
Moved API documentation to api-framework README
daniellockyer Aug 11, 2022
dfdcb98
Lazily executed calculating headers in API framework
daniellockyer Aug 12, 2022
358c29b
Added tests to HTTP module of api-framework
daniellockyer Aug 12, 2022
86c6340
Refactored api-framework to use optional chaining
daniellockyer Aug 12, 2022
801a04a
Fixed full Admin test suite running during unit tests
daniellockyer Aug 15, 2022
4386ac1
Organized package dependencies
daniellockyer Aug 18, 2022
5c50c56
Fixed some type issues with the api framework
ErisDS Aug 21, 2022
10563cf
Updated @tryghost dependencies (#15404)
renovate[bot] Sep 19, 2022
767f080
Updated @tryghost dependencies (#15434)
renovate[bot] Sep 27, 2022
1f11a6e
Update Test & linting packages
renovate[bot] Oct 5, 2022
e645796
Updated @tryghost dependencies (#15479)
renovate[bot] Oct 12, 2022
1d3495e
Update dependency mocha to v10.1.0
renovate[bot] Oct 17, 2022
b1c2fae
Updated @tryghost dependencies (#15631)
renovate[bot] Oct 24, 2022
6760abf
Removed bluebird from `api-framework` module (#15685)
iamhalldor Oct 31, 2022
59f0397
Update Test & linting packages
renovate[bot] Nov 7, 2022
2c68b74
Update dependency mocha to v10.2.0
renovate[bot] Dec 12, 2022
0a25455
Updated @tryghost dependencies (#16005)
daniellockyer Dec 14, 2022
807973e
Bumped TryGhost-owned dependencies and lockfile
daniellockyer Jan 2, 2023
8be7906
Update dependency c8 to v7.13.0
renovate[bot] Feb 16, 2023
6157cc3
Updated `@tryghost/errors` dependency
daniellockyer Feb 22, 2023
5889c47
Added cache support to api-framework
naz Feb 22, 2023
b2e2a80
Update @tryghost
renovate[bot] Feb 27, 2023
4a1880f
Updated `sinon` dependency
daniellockyer Mar 2, 2023
11e50b8
Update Test & linting packages
renovate[bot] Mar 13, 2023
661b46e
Added yarn resolution for `@tryghost/errors`
daniellockyer Apr 5, 2023
e276d00
Removed heavy dependency within `@tryghost/errors`
daniellockyer Apr 5, 2023
783b0ce
Update Test & linting packages
renovate[bot] Apr 5, 2023
36afc72
Update @tryghost
renovate[bot] Apr 7, 2023
5767c9c
Added eslint rule for file naming convention
allouis May 2, 2023
0330b04
✨ Implemented duplicate post functionality (#16767)
mike182uk May 15, 2023
3a2fcb1
Update Test & linting packages
renovate[bot] Jul 11, 2023
37f7ed8
Added function names to all Express middleware
daniellockyer Aug 30, 2023
12ff4c7
Updated linting and testing packages
daniellockyer Sep 1, 2023
5388217
Added support for custom cache key generation
allouis Sep 5, 2023
421a54b
Configured all unit tests to use dot reporter
daniellockyer Oct 5, 2023
4398d08
Aligned dependencies with resolution values
daniellockyer Oct 12, 2023
352bbd9
Update TryGhost packages
renovate[bot] Oct 31, 2023
0e720f0
Added support for "Refresh Ahead" caching strategy (#19499)
allouis Jan 17, 2024
62fed6d
Revert "Added support for "Refresh Ahead" caching strategy" (#19502)
Jan 17, 2024
b650919
Refactored the pipeline execution to async fn
allouis Jan 17, 2024
685d4a1
Implemented Refresh-Ahead caching for Redis
allouis Jan 17, 2024
e9dffee
Supported setting headers on a per-request basis
allouis Feb 27, 2024
b74e547
Ensured that endpoint header config is not modified in future
allouis Feb 27, 2024
fb73dbc
Updated `@tryghost/errors` dependency
daniellockyer Mar 11, 2024
36cfa9c
Cached api controller pipelines (#19880)
allouis Mar 18, 2024
77274db
Update TryGhost packages
renovate[bot] Apr 30, 2024
23fa94a
Update TryGhost packages
renovate[bot] May 1, 2024
8e550a4
Fixed miscellaneous jsdoc comments
daniellockyer May 6, 2024
db2d51b
Added JSDoc types for API controllers
daniellockyer May 7, 2024
d8b1ee4
Rolled out API framework JSDoc typing to more places
daniellockyer May 8, 2024
42a1669
Excluded `docName` key from API controller method map
daniellockyer May 8, 2024
d5a809a
Renamed `wrapper` to `ImplWrapper`
daniellockyer May 13, 2024
69ee84c
Update TryGhost packages
renovate[bot] May 27, 2024
b208f5d
Fixed handling objects as API input parameters
daniellockyer Jun 24, 2024
ffc1a71
Update TryGhost packages
renovate[bot] Jul 30, 2024
4d4d2ff
Update dependency mocha to v10.8.2 (#22704)
renovate[bot] Mar 29, 2025
bb8a613
Bumped packages to use tryghost/errors 1.3.7 (#23039)
9larsons Apr 24, 2025
84f32e4
Update TryGhost packages (#20735)
renovate[bot] Apr 26, 2025
9175f55
Prepared packages for initial publishing
daniellockyer May 13, 2025
caa6922
Published new versions
daniellockyer May 13, 2025
fb1ea3d
Added missing dependency
daniellockyer May 13, 2025
5849ef5
Published new versions
daniellockyer May 13, 2025
3bd0627
Updated repository references
ErisDS May 17, 2025
9cc6775
Updated repository in package.json
ErisDS May 25, 2025
55c0872
Published new versions
ErisDS May 25, 2025
d33608d
Update dependency lodash to v4.17.23 [SECURITY] (#346)
renovate[bot] Jan 27, 2026
eb0d979
Update dependency sinon to v21 (#212)
renovate[bot] Jan 27, 2026
5ecec0b
Update dependency mocha to v11 (#174)
renovate[bot] Jan 27, 2026
b7f5b1a
fix(deps): update dependency jsonpath to v1.2.0 (#357)
renovate[bot] Feb 5, 2026
9752e2d
fix(deps): update dependency jsonpath to v1.2.1 (#359)
renovate[bot] Feb 6, 2026
a3bd6b5
Removed jsonpath dependency from api-framework
allouis Feb 11, 2026
5cc63fa
Published new versions
allouis Feb 12, 2026
c8abd24
Published new versions
9larsons Feb 20, 2026
ea60d6b
Updated readmes and descriptions with basic info
9larsons Feb 20, 2026
92303ca
Refactored api-framework tests to use assert and updated coverage checks
9larsons Feb 20, 2026
d72882f
Published new versions
9larsons Feb 20, 2026
07ca3ac
Published new versions
9larsons Feb 20, 2026
4834fa5
Published new versions
9larsons Feb 20, 2026
0d94980
Published new versions
9larsons Feb 21, 2026
9e34299
chore(deps): update test & linting packages (major) (#179)
renovate[bot] Feb 21, 2026
c49348a
Published new versions
9larsons Feb 21, 2026
aeca378
Published new versions
9larsons Feb 21, 2026
1936f78
Published new versions
9larsons Feb 21, 2026
aaf8866
Update dependency c8 to v11 (#404)
renovate[bot] Feb 25, 2026
83b3bdf
Published new versions
9larsons Mar 2, 2026
4917d75
Remove json-stable-stringify from @tryghost/api-framework (#428)
9larsons Mar 2, 2026
1d061d1
Replaced Mocha with Vitest across all packages (#429)
9larsons Mar 2, 2026
c3c588f
Update dependency sinon to v21.0.2 (#439)
renovate[bot] Mar 7, 2026
df50a18
Remove leftover mocha devDependencies from all packages (#449)
9larsons Mar 10, 2026
7c703e3
Cleaned up eslint config (#452)
9larsons Mar 10, 2026
6144599
Published new versions
9larsons Mar 10, 2026
d9e8d6d
Published new versions
9larsons Mar 11, 2026
4e56e58
Published new versions
kevinansfield Mar 16, 2026
df19aad
Update dependency sinon to v21.0.3 (#466)
renovate[bot] Mar 19, 2026
5251c0a
Update dependency lodash to v4.18.1 [SECURITY] (#501)
renovate[bot] Apr 2, 2026
0bfeb3c
Update dependency sinon to v21.1.0 (#525)
renovate[bot] Apr 12, 2026
7436071
Update dependency sinon to v21.1.1 (#532)
renovate[bot] Apr 14, 2026
44fd97a
Update dependency sinon to v21.1.2 (#536)
renovate[bot] Apr 14, 2026
ea5c67f
Switched from eslint to oxlint + oxfmt (#544)
9larsons Apr 21, 2026
e8b2dd8
Migrated from yarn to pnpm 10.33 (#549)
9larsons Apr 21, 2026
9887986
Converted internal deps to pnpm workspace: protocol (#554)
9larsons Apr 22, 2026
2267afb
Move pnpm config to workspace.yaml, catalog shared devDeps (#557)
9larsons Apr 22, 2026
3b0d9d4
Updated package READMEs: yarn -> pnpm, eslint -> oxlint, lerna -> Nx …
9larsons Apr 22, 2026
055825f
Published new versions
9larsons Apr 22, 2026
9f6a44a
Fixed cache key serialization dropping nested object keys (#561)
rob-ghost Apr 23, 2026
c32aa6f
Tightened CI workflows (#563)
9larsons Apr 23, 2026
1ba0e82
Standardize per-package test scripts (#570)
9larsons Apr 23, 2026
98b06b1
Published new versions
9larsons Apr 23, 2026
02489db
Published new versions
9larsons Apr 27, 2026
b5e37a9
Published new versions
ErisDS May 14, 2026
3aac1d8
Published new versions
sam-lord May 26, 2026
9925cd2
Updated API input validators (#689)
jonatansberg Jun 1, 2026
0dadff6
Published new versions
jonatansberg Jun 1, 2026
7ec1e30
Published new versions
kevinansfield Jun 12, 2026
4ae9bb2
Moved @tryghost/api-framework into the Ghost monorepo
allouis Jun 18, 2026
cdeaee3
Integrated @tryghost/api-framework into the pnpm + Nx workspace
allouis Jun 18, 2026
2e48e47
Added oxlint + oxfmt lint setup for api-framework
allouis Jun 18, 2026
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
13 changes: 13 additions & 0 deletions ghost/api-framework/.oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"singleQuote": true,
"ignorePatterns": [
"**/node_modules/**",
"**/build/**",
"**/coverage/**",
"**/cjs/**",
"**/es/**",
"**/types/**",
"**/*.hbs",
"**/test/fixtures/**"
]
}
12 changes: 12 additions & 0 deletions ghost/api-framework/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": ["node", "typescript"],
"ignorePatterns": [
"**/node_modules/**",
"**/build/**",
"**/coverage/**",
"**/cjs/**",
"**/es/**",
"**/types/**",
"**/test/fixtures/**"
]
}
150 changes: 150 additions & 0 deletions ghost/api-framework/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# API Framework

API framework used by Ghost

## Purpose

Composable framework for Ghost API controllers, request framing, validation/serialization pipelines, and HTTP response helpers.

## Usage

### Stages

Each request goes through the following stages:

- input validation
- input serialisation
- permissions
- query
- output serialisation

The framework we are building pipes a request through these stages in respect of the API controller configuration.

### Frame

Is a class, which holds all the information for request processing. We pass this instance by reference.
Each function can modify the original instance. No need to return the class instance.

#### Structure

```
{
original: Object,
options: Object,
data: Object,
user: Object,
file: Object,
files: Array
}
```

#### Example

```
{
original: {
include: 'tags'
},
options: {
withRelated: ['tags']
},
data: {
posts: []
}
}
```

### API Controller

A controller is no longer just a function, it's a set of configurations.

#### Structure

```
edit: function || object
```

```
edit: {
headers: object,
options: Array,
data: Array,
validation: object | function,
permissions: boolean | object | function,
query: function
}
```

#### Examples

```
edit: {
headers: {
cacheInvalidate: true
},
// Allowed url/query params
options: ['include']
// Url/query param validation configuration
validation: {
options: {
include: {
required: true,
values: ['tags']
}
}
},
permissions: true,
// Returns a model response!
query(frame) {
return models.Post.edit(frame.data, frame.options);
}
}
```

```
read: {
// Allowed url/query params, which will be remembered inside `frame.data`
// This is helpful for READ requests e.g. `model.findOne(frame.data, frame.options)`.
// Our model layer requires sending the where clauses as first parameter.
data: ['slug']
validation: {
data: {
slug: {
values: ['eins']
}
}
},
permissions: true,
query(frame) {
return models.Post.findOne(frame.data, frame.options);
}
}
```

```
edit: {
validation() {
// custom validation, skip framework
},
permissions: {
unsafeAttrs: ['author']
},
query(frame) {
return models.Post.edit(frame.data, frame.options);
}
}
```

## Develop

This is a monorepo package.

Follow the instructions for the top-level repo.

1. `git clone` this repo & `cd` into it as usual
2. Run `pnpm install` to install top-level dependencies.

## Test

- `pnpm lint` runs oxlint
- `pnpm test` runs lint and tests
27 changes: 27 additions & 0 deletions ghost/api-framework/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** @typedef {object} PermissionsObject */
/** @typedef {boolean} PermissionsBoolean */

/** @typedef {number} StatusCodeNumber */
/** @typedef {(result: any) => number} StatusCodeFunction */

/** @typedef {object} ValidationObject */

/**
* @typedef {object} ControllerMethod
* @property {object} headers
* @property {PermissionsBoolean | PermissionsObject} permissions
* @property {string[]} [options]
* @property {ValidationObject} [validation]
* @property {string[]} [data]
* @property {StatusCodeFunction | StatusCodeNumber} [statusCode]
* @property {object} [response]
* @property {function} [cache]
* @property {(frame: import('./lib/Frame')) => object} [generateCacheKeyData]
* @property {(frame: import('./lib/Frame')) => any} query
*/

/**
* @typedef {Record<string, ControllerMethod | string> & Record<'docName', string>} Controller
*/

module.exports = require('./lib/api-framework');
110 changes: 110 additions & 0 deletions ghost/api-framework/lib/Frame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const debug = require('@tryghost/debug')('frame');
const _ = require('lodash');

/**
* @description The "frame" holds all information of a request.
*
* Each party can modify the frame by reference.
* A request hits a lot of stages in the API implementation and that's why modification by reference was the
* easiest to use. We always have access to the original input, we never loose track of it.
*/
class Frame {
#headers = {};
constructor(obj = {}) {
this.original = obj;

/**
* options: Query params, url params, context and custom options
* data: Body or if the ctrl wants query/url params inside body
* user: Logged in user
* file: Uploaded file
* files: Uploaded files
* apiType: Content or admin api access
* docName: The endpoint name, e.g. "posts"
* method: The method name, e.g. "browse"
*/
this.options = {};
this.data = {};
this.user = {};
this.file = {};
this.files = [];
this.#headers = {};
this.apiType = null;
this.docName = null;
this.method = null;
this.response = null;
}

/**
* @description Configure the frame.
*
* If you instantiate a new frame, all the data you pass in, land in `this.original`. This is helpful
* for debugging to see what the original input was.
*
* This function will prepare the incoming data for further processing.
* Based on the API ctrl implemented, this fn will pick allowed properties to either options or data.
*/
configure(apiConfig) {
debug('configure');

if (apiConfig.options) {
if (typeof apiConfig.options === 'function') {
apiConfig.options = apiConfig.options(this);
}

if (Object.prototype.hasOwnProperty.call(this.original, 'query')) {
Object.assign(this.options, _.pick(this.original.query, apiConfig.options));
}

if (Object.prototype.hasOwnProperty.call(this.original, 'params')) {
Object.assign(this.options, _.pick(this.original.params, apiConfig.options));
}

if (Object.prototype.hasOwnProperty.call(this.original, 'options')) {
Object.assign(this.options, _.pick(this.original.options, apiConfig.options));
}
}

this.options.context = this.original.context;

if (this.original.body && Object.keys(this.original.body).length) {
this.data = _.cloneDeep(this.original.body);
} else {
if (apiConfig.data) {
if (typeof apiConfig.data === 'function') {
apiConfig.data = apiConfig.data(this);
}

if (Object.prototype.hasOwnProperty.call(this.original, 'query')) {
Object.assign(this.data, _.pick(this.original.query, apiConfig.data));
}

if (Object.prototype.hasOwnProperty.call(this.original, 'params')) {
Object.assign(this.data, _.pick(this.original.params, apiConfig.data));
}

if (Object.prototype.hasOwnProperty.call(this.original, 'options')) {
Object.assign(this.data, _.pick(this.original.options, apiConfig.data));
}
}
}

this.user = this.original.user;
this.file = this.original.file;
this.files = this.original.files;

debug('original', this.original);
debug('options', this.options);
debug('data', this.data);
}

setHeader(header, value) {
this.#headers[header] = value;
}

getHeaders() {
return Object.assign({}, this.#headers);
}
}

module.exports = Frame;
29 changes: 29 additions & 0 deletions ghost/api-framework/lib/api-framework.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module.exports = {
get headers() {
return require('./headers');
},

get http() {
return require('./http');
},

get Frame() {
return require('./Frame');
},

get pipeline() {
return require('./pipeline');
},

get validators() {
return require('./validators');
},

get serializers() {
return require('./serializers');
},

get utils() {
return require('./utils');
},
};
Loading
Loading