Skip to content

refactor(core): enhance class implementations and documentation #162

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
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 3 additions & 5 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"import",
"eslint",
"oxc",
"promise"
"promise",
"jsdoc"
],
"rules": {
"@typescript-eslint/no-empty-object-type": [
Expand All @@ -30,14 +31,13 @@
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-declaration-merging": "off",
"@typescript-eslint/no-unsafe-declaration-merging": "error",
"eslint/arrow-body-style": ["error", "as-needed"],
"eslint/curly": "error",
"eslint/id-length": "off",
"eslint/max-depth": "off",
"eslint/max-lines": "off",
"eslint/max-lines-per-function": "off",
"eslint/max-nested-callbacks": "off",
"eslint/no-duplicate-imports": "off",
"func-style": [
"error",
Expand Down Expand Up @@ -90,9 +90,7 @@
}
}
],
"unicorn/no-nested-ternary": "off",
"unicorn/no-null": "off",
"unicorn/number-literal-case": "off",
"vitest/consistent-test-it": [
"error",
{
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"import": "./lib/index.js",
"default": "./lib/index.js"
},
"./core": {
"types": "./lib/core/index.d.ts",
"import": "./lib/core/index.js",
"default": "./lib/core/index.js"
},
"./package.json": "./package.json"
},
"main": "./lib/index.js",
Expand All @@ -47,7 +52,7 @@
"knip": "knip",
"knip:fix": "knip --fix",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"lint:fix": "oxlint --fix --fix-suggestions",
"test": "vitest run",
"test:coverage": "vitest --coverage",
"test:watch": "vitest --watch",
Expand Down Expand Up @@ -86,7 +91,7 @@
},
"runtime": {
"name": "node",
"version": ">=20"
"version": ">= 20"
}
}
}
10 changes: 6 additions & 4 deletions src/core/Api.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { Resource } from "./Resource.js";
import type { Nullable } from "./types.js";
import { assignSealed } from "./utils/index.js";

export interface ApiOptions
extends Nullable<{
title?: string;
resources?: Resource[];
}> {}

export interface Api extends ApiOptions {}
export class Api {
export class Api implements ApiOptions {
entrypoint: string;

title?: string | null;
resources?: Resource[] | null;

constructor(entrypoint: string, options: ApiOptions = {}) {
this.entrypoint = entrypoint;
assignSealed(this, options);
Object.assign(this, options);
}
}
22 changes: 17 additions & 5 deletions src/core/Field.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Resource } from "./Resource.js";
import type { Nullable } from "./types.js";
import { assignSealed } from "./utils/index.js";

export type FieldType =
| "string"
Expand Down Expand Up @@ -40,18 +39,31 @@ export interface FieldOptions
enum?: { [key: string | number]: string | number };
reference?: string | Resource;
embedded?: Resource;
required?: boolean;
nullable?: boolean;
required?: boolean;
description?: string;
maxCardinality?: number;
deprecated?: boolean;
}> {}

export interface Field extends FieldOptions {}
export class Field {
export class Field implements FieldOptions {
name: string;

id?: string | null;
range?: string | null;
type?: FieldType | null;
arrayType?: FieldType | null;
enum?: { [key: string | number]: string | number } | null;
reference?: string | Resource | null;
embedded?: Resource | null;
nullable?: boolean | null;
required?: boolean | null;
description?: string | null;
maxCardinality?: number | null;
deprecated?: boolean | null;

constructor(name: string, options: FieldOptions = {}) {
this.name = name;
assignSealed(this, options);
Object.assign(this, options);
}
}
13 changes: 9 additions & 4 deletions src/core/Operation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Nullable } from "./types.js";
import { assignSealed } from "./utils/index.js";

export type OperationType = "show" | "edit" | "delete" | "list" | "create";

Expand All @@ -12,10 +11,16 @@ export interface OperationOptions
deprecated?: boolean;
}> {}

export interface Operation extends OperationOptions {}
export class Operation {
export class Operation implements OperationOptions {
name: string;
type: OperationType;

method?: string | null;
expects?: any | null;
returns?: string | null;
types?: string[] | null;
deprecated?: boolean | null;

constructor(
name: string,
type: OperationType,
Expand All @@ -24,6 +29,6 @@ export class Operation {
this.name = name;
this.type = type;

assignSealed(this, options);
Object.assign(this, options);
}
}
22 changes: 16 additions & 6 deletions src/core/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,39 @@ import type { Field } from "./Field.js";
import type { Operation } from "./Operation.js";
import type { Parameter } from "./Parameter.js";
import type { Nullable } from "./types.js";
import { assignSealed } from "./utils/index.js";

export interface ResourceOptions
extends Nullable<{
id?: string;
title?: string;
description?: string;
deprecated?: boolean;
fields?: Field[];
readableFields?: Field[];
writableFields?: Field[];
parameters?: Parameter[];
getParameters?: () => Promise<Parameter[]>;
operations?: Operation[];
deprecated?: boolean;
parameters?: Parameter[];
}> {}

export interface Resource extends ResourceOptions {}
export class Resource {
export class Resource implements ResourceOptions {
name: string;
url: string;

id?: string | null;
title?: string | null;
description?: string | null;
fields?: Field[] | null;
readableFields?: Field[] | null;
writableFields?: Field[] | null;
getParameters?: (() => Promise<Parameter[]>) | null;
operations?: Operation[] | null;
deprecated?: boolean | null;
parameters?: Parameter[] | null;

constructor(name: string, url: string, options: ResourceOptions = {}) {
this.name = name;
this.url = url;
assignSealed(this, options);
Object.assign(this, options);
}
}
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./Field.js";
export * from "./Operation.js";
export * from "./Parameter.js";
export * from "./Resource.js";
export * from "./types.js";
13 changes: 0 additions & 13 deletions src/core/utils/assignSealed.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/core/utils/buildEnumObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { humanize } from "inflection";
* The keys of the object are the humanized versions of the enum values,
* and the values are the original enum values.
*
* @param enumArray - An array of enum values.
* @returns An object mapping humanized enum names to their original values, or null if the input is empty.
* @param {any[] | undefined} enumArray - An array of enum values.
* @returns {Record<string, string | number> | null} An object mapping humanized enum names to their original values, or null if the input is empty.
*/
export function buildEnumObject(
enumArray: any[] | undefined,
Expand Down
6 changes: 3 additions & 3 deletions src/core/utils/getType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import type { FieldType } from "../Field.js";
* If a format is provided, it will map certain formats (e.g., "int32", "int64") to "integer".
* Otherwise, it will camelize the format string. If no format is provided, it returns the OpenAPI type.
*
* @param openApiType - The OpenAPI type string.
* @param format - An optional format string.
* @returns The mapped FieldType.
* @param {string} openApiType - The OpenAPI type string.
* @param {string} [format] - An optional format string.
* @returns {FieldType} The mapped FieldType.
*/
export function getType(openApiType: string, format?: string): FieldType {
if (format) {
Expand Down
1 change: 0 additions & 1 deletion src/core/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { assignSealed } from "./assignSealed.js";
export { buildEnumObject } from "./buildEnumObject.js";
export { getResourcePaths } from "./getResourcePaths.js";
export { getType } from "./getType.js";
Expand Down
3 changes: 3 additions & 0 deletions src/hydra/fetchJsonLd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ interface ResponseDocument extends RemoteDocument {

/**
* Sends a JSON-LD request to the API.
* @param {string} url The URL to request.
* @param {RequestInitExtended} [options] Optional fetch options.
* @returns {Promise<ResponseDocument | EmptyResponseDocument>} The response document or an empty response document.
*/
export default async function fetchJsonLd(
url: string,
Expand Down
36 changes: 34 additions & 2 deletions src/hydra/parseHydraDocumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ import type {

/**
* Extracts the short name of a resource.
* @param {string} url The resource URL.
* @param {string} entrypointUrl The API entrypoint URL.
* @returns {string} The short name of the resource.
*/
function guessNameFromUrl(url: string, entrypointUrl: string): string {
return url.slice(entrypointUrl.length + 1);
}

/**
* Gets the title or label from an ExpandedOperation object.
* @param {ExpandedOperation} obj The operation object.
* @returns {string} The title or label.
*/
function getTitleOrLabel(obj: ExpandedOperation): string {
const a =
obj["http://www.w3.org/2000/01/rdf-schema#label"] ??
Expand All @@ -36,6 +44,9 @@ function getTitleOrLabel(obj: ExpandedOperation): string {

/**
* Finds the description of the class with the given id.
* @param {ExpandedDoc[]} docs The expanded documentation array.
* @param {string} classToFind The class ID to find.
* @returns {ExpandedClass} The matching expanded class.
*/
function findSupportedClass(
docs: ExpandedDoc[],
Expand Down Expand Up @@ -87,6 +98,9 @@ export function getDocumentationUrlFromHeaders(headers: Headers): string {

/**
* Retrieves Hydra's entrypoint and API docs.
* @param {string} entrypointUrl The URL of the API entrypoint.
* @param {RequestInitExtended} [options] Optional fetch options.
* @returns {Promise<{ entrypointUrl: string; docsUrl: string; response: Response; entrypoint: Entrypoint[]; docs: ExpandedDoc[]; }>} An object containing entrypointUrl, docsUrl, response, entrypoint, and docs.
*/
async function fetchEntrypointAndDocs(
entrypointUrl: string,
Expand All @@ -98,6 +112,11 @@ async function fetchEntrypointAndDocs(
entrypoint: Entrypoint[];
docs: ExpandedDoc[];
}> {
/**
* Loads a JSON-LD document from the given input.
* @param {string} input The URL or IRI to load.
* @returns {Promise<any>} The fetched JSON-LD response.
*/
async function documentLoader(input: string) {
const response = await fetchJsonLd(input, options);
if (!("body" in response)) {
Expand Down Expand Up @@ -154,6 +173,12 @@ async function fetchEntrypointAndDocs(
}
}

/**
* Finds the related class for a property.
* @param {ExpandedDoc[]} docs The expanded documentation array.
* @param {ExpandedRdfProperty} property The property to find the related class for.
* @returns {ExpandedClass} The related expanded class.
*/
function findRelatedClass(
docs: ExpandedDoc[],
property: ExpandedRdfProperty,
Expand Down Expand Up @@ -213,6 +238,9 @@ function findRelatedClass(

/**
* Parses Hydra documentation and converts it to an intermediate representation.
* @param {string} entrypointUrl The API entrypoint URL.
* @param {RequestInitExtended} [options] Optional fetch options.
* @returns {Promise<{ api: Api; response: Response; status: number; }>} The parsed API, response, and status.
*/
export default async function parseHydraDocumentation(
entrypointUrl: string,
Expand Down Expand Up @@ -470,8 +498,12 @@ export default async function parseHydraDocumentation(
});

resource.parameters = [];
resource.getParameters = (): Promise<Parameter[]> =>
getParameters(resource, options);
resource.getParameters =
/**
* Gets the parameters for the resource.
* @returns {Promise<Parameter[]>} The parameters for the resource.
*/
(): Promise<Parameter[]> => getParameters(resource, options);

resources.push(resource);
}
Expand Down
Loading