-
-
Notifications
You must be signed in to change notification settings - Fork 47
WIP: kitsu-core typescript implementations & JSON:API v1.1 type definitions #930
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
Open
pedep
wants to merge
14
commits into
wopian:refactor/typescript
Choose a base branch
from
pedep:refactor/typescript
base: refactor/typescript
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
3d5dd0d
implement a few interfaces from the specification
pedep 6c56275
refactor deattribute using new interfaces and type guards
pedep 617c11b
implement isDeepEqual
pedep 7202f7d
fix linting and apply isAttribute in _deattribute
pedep c84d29c
expand comment with another option
pedep 7ee07a3
add test for better coverage in deepEqual
pedep e26f850
bump LTS version of node in CI
pedep dd4b942
use correct import path
pedep ca67dca
implement query
pedep b4214b4
implement serialise & error
pedep 72694d2
specs: cover remaining branch
pedep 099e17c
remove dependency on axios, use local partial interface
pedep 0e8342d
implement filterIncludes
pedep be8ac30
implement splitModel
pedep File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,48 @@ | ||
interface Data { | ||
id: string | ||
type: string | ||
attributes?: { | ||
[key: string]: | ||
| string | ||
| number | ||
| boolean | ||
| null | ||
| undefined | ||
| object | ||
| object[] | ||
| string[] | ||
| number[] | ||
| boolean[] | ||
} | ||
} | ||
import { Attributes, isAttributes } from '../resources/attributes.js' | ||
import { ResourceIdentifier } from '../resources/resourceIdentifier.js' | ||
import { ResourceObject } from '../resources/resourceObject.js' | ||
|
||
export type DeattributedResourceObject = ResourceIdentifier & Attributes | ||
|
||
// Write a function that hoists the attributes of a given object to the top level | ||
export const deattribute = (data: Data | Data[]): Data | Data[] => { | ||
let output = data | ||
if (Array.isArray(data)) output = data.map(deattribute) as Data[] | ||
else if ( | ||
typeof data.attributes === 'object' && | ||
data.attributes !== null && | ||
!Array.isArray(data.attributes) | ||
) { | ||
output = { | ||
...data, | ||
...data.attributes | ||
} as Data | ||
export function deattribute(data: ResourceObject): DeattributedResourceObject | ||
export function deattribute( | ||
data: ResourceObject[] | ||
): DeattributedResourceObject[] | ||
export function deattribute( | ||
data: ResourceObject | ResourceObject[] | ||
): DeattributedResourceObject | DeattributedResourceObject[] { | ||
return isResourceObjectArray(data) | ||
? data.map(_deattribute) | ||
: _deattribute(data) | ||
} | ||
|
||
if (output.attributes === data.attributes) delete output.attributes | ||
function _deattribute(data: ResourceObject): DeattributedResourceObject { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be named |
||
// FIXME: what is the best behaviour when given an invalid attributes key? | ||
// 1. (Current) the same invalid object is returned. | ||
// a. This results in deattribute returning potentially invalid DeattributedResourceObjects | ||
// b. Change the return type to include this scenario. Doing this will possibly cause issues | ||
// down the road in kitsu and kitsu-core | ||
// 2. the object is modified, and has the invalid key removed | ||
// a. This would guarantee valid returns, but will also change the current default behaviour. | ||
// 3. the object is not touched, and an error is thrown | ||
// a. this would function closer to how JSON.parse does, throwing errors when unexpected input is given | ||
// | ||
// This should not be an issue for projects using typescript natively, since the compiler will warn when passing | ||
// objects with mismatched types to deattribute | ||
if (!isAttributes(data.attributes)) return data as DeattributedResourceObject | ||
|
||
const output = { | ||
...data, | ||
...data.attributes | ||
} | ||
|
||
if (output.attributes === data.attributes) delete output.attributes | ||
return output | ||
} | ||
|
||
function isResourceObjectArray( | ||
object: ResourceObject | ResourceObject[] | ||
): object is ResourceObject[] { | ||
return Array.isArray(object) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import test from 'ava' | ||
|
||
import { isDeepEqual } from '../index.js' | ||
|
||
const people = { | ||
one: { | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
age: 35 | ||
}, | ||
two: { | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
age: 35 | ||
}, | ||
three: { | ||
firstName: 'Akash', | ||
lastName: 'Thakur', | ||
age: 35 | ||
}, | ||
four: { | ||
firstName: 'Jane', | ||
lastName: 'Doe' | ||
}, | ||
five: { | ||
address: { | ||
street: '123 Main St', | ||
inhabitants: ['Chuck', 'Howard', { name: 'Jimmy', age: 35 }] | ||
} | ||
}, | ||
six: { | ||
address: { | ||
street: '123 Main St', | ||
inhabitants: ['Chuck', 'Howard', { name: 'Jimmy', age: 35 }] | ||
} | ||
}, | ||
seven: { | ||
address: { | ||
street: '456 Main St', | ||
inhabitants: ['Chuck', 'Howard', { name: 'Jimmy', age: 35 }] | ||
} | ||
}, | ||
eight: { | ||
address: { | ||
street: '123 Main St', | ||
inhabitants: ['Howard', { name: 'Jimmy', age: 35 }, 'Chuck'] | ||
} | ||
} | ||
} | ||
|
||
test('checks if both objects are truthy', t => { | ||
t.false(isDeepEqual(people.one, false)) | ||
t.notDeepEqual(people.one, false) | ||
|
||
t.false(isDeepEqual(false, people.one)) | ||
t.notDeepEqual(false, people.one) | ||
|
||
t.false(isDeepEqual(false, 0)) | ||
t.notDeepEqual(false, 0) | ||
}) | ||
|
||
test('checks identical objects are equal', t => { | ||
t.true(isDeepEqual(people.one, people.two)) | ||
t.deepEqual(people.one, people.two) | ||
}) | ||
|
||
test('checks different objects are not equal', t => { | ||
t.false(isDeepEqual(people.one, people.three)) | ||
t.notDeepEqual(people.one, people.three) | ||
}) | ||
|
||
test('checks objects have the same number of keys', t => { | ||
t.false(isDeepEqual(people.one, people.four)) | ||
t.notDeepEqual(people.one, people.four) | ||
}) | ||
|
||
test('checks nested objects are equal', t => { | ||
t.true(isDeepEqual(people.five, people.six)) | ||
t.deepEqual(people.five, people.six) | ||
|
||
t.false(isDeepEqual(people.five, people.seven)) | ||
t.notDeepEqual(people.five, people.seven) | ||
|
||
t.false(isDeepEqual(people.five, people.eight)) | ||
t.notDeepEqual(people.five, people.eight) | ||
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// isDeepEqual is able to compare every possible input, so we allow explicit any | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
type Comparable = any | ||
|
||
export function isDeepEqual(left: Comparable, right: Comparable): boolean { | ||
if (!left || !right) return left === right | ||
|
||
const leftKeys = Object.keys(left) | ||
const rightKeys = Object.keys(right) | ||
|
||
if (leftKeys.length !== rightKeys.length) return false | ||
|
||
for (const key of leftKeys) { | ||
const leftValue = left[key] | ||
const rightValue = right[key] | ||
|
||
const goDeeper = isDeep(leftValue) && isDeep(rightValue) | ||
|
||
if ( | ||
(goDeeper && !isDeepEqual(leftValue, rightValue)) || | ||
(!goDeeper && leftValue !== rightValue) | ||
) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
function isDeep(object: unknown): boolean { | ||
return typeof object === 'object' && object !== null | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import test from 'ava' | ||
|
||
import { error } from '../index.js' | ||
|
||
test('handles axios response errors', t => { | ||
t.plan(1) | ||
|
||
const object = { response: {} } | ||
try { | ||
error(object) | ||
} catch (error_: unknown) { | ||
t.deepEqual(error_, { response: {} }) | ||
} | ||
}) | ||
|
||
test('throws all other errors', t => { | ||
t.plan(2) | ||
|
||
try { | ||
error('Hello') | ||
} catch (error_: unknown) { | ||
t.is(error_, 'Hello') | ||
} | ||
|
||
t.throws( | ||
() => { | ||
error(new Error('Hello')) | ||
}, | ||
{ message: 'Hello' } | ||
) | ||
}) | ||
|
||
test('handles axios response errors with JSON:API errors', t => { | ||
t.plan(1) | ||
const object = { | ||
response: { | ||
data: { | ||
errors: [ | ||
{ | ||
title: 'Filter is not allowed', | ||
detail: 'x is not allowed', | ||
code: '102', | ||
status: '400' | ||
} | ||
] | ||
} | ||
} | ||
} | ||
try { | ||
error(object) | ||
} catch ({ errors }) { | ||
t.deepEqual(errors, [ | ||
{ | ||
title: 'Filter is not allowed', | ||
detail: 'x is not allowed', | ||
code: '102', | ||
status: '400' | ||
} | ||
]) | ||
} | ||
}) | ||
|
||
test('handles top-level JSON:API errors', t => { | ||
t.plan(1) | ||
const object = { | ||
errors: [{ code: 400 }] | ||
} | ||
try { | ||
error(object) | ||
} catch (error_) { | ||
t.deepEqual(error_, { | ||
errors: [{ code: 400 }] | ||
}) | ||
} | ||
}) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.