Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@hyperjump/browser

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hyperjump/browser - npm Package Compare versions

Comparing version 1.0.0 to 1.1.0

80

lib/browser/browser.js
import curry from "just-curry-it";
import { get as pointerGet, append as pointerAppend } from "@hyperjump/json-pointer";
import { resolveIri, parseIri, toAbsoluteIri } from "@hyperjump/uri";
import { parseIri, resolveIri, toAbsoluteIri } from "@hyperjump/uri";
import { contextUri } from "./context-uri.js";
import { retrieve } from "../uri-schemes/uri-schemes.js";
import { parseResponse } from "../media-types/media-types.js";
import { Reference } from "../jref/index.js";
import { jrefTypeOf } from "../jref/index.js";
export const get = async (uri, document = undefined) => {
const baseUri = document ? document.baseUri : contextUri();
export const get = async (uri, browser = { _cache: {} }) => {
const baseUri = browser.document ? browser.document.baseUri : contextUri();
uri = resolveIri(uri, baseUri);
const id = toAbsoluteIri(uri);
const { fragment } = parseIri(uri);
let responseDocument;
if (document && toAbsoluteIri(uri) === document.baseUri) {
const { fragment } = parseIri(uri);
responseDocument = {
...document,
cursor: fragment,
_value: pointerGet(document.cursor, document.root)
};
const cachedDocument = browser._cache[id] ?? browser.document?.embedded?.[id];
if (cachedDocument) {
browser.document = cachedDocument;
browser.uri = uri;
browser.cursor = browser.document.anchorLocation(fragment);
} else {
try {
const { response, fragment } = await retrieve(uri, document);
responseDocument = await parseResponse(response, fragment);
responseDocument._value = pointerGet(responseDocument.cursor, responseDocument.root);
const response = await retrieve(uri, baseUri);
browser.document = await parseResponse(response);
browser.uri = response.url + (fragment === undefined ? "" : `#${fragment}`);
browser.cursor = browser.document.anchorLocation(fragment);
} catch (error) {
const referencedMessage = document ? ` Referenced from '${document.baseUri}#${document.cursor}'.` : "";
const referencedMessage = browser.uri ? ` Referenced from '${browser.uri}'.` : "";
throw new RetrievalError(`Unable to load resource '${uri}'.${referencedMessage}`, error);
}
browser._cache[id] = browser.document;
}
return followReferences(responseDocument);
browser._value = pointerGet(browser.cursor, browser.document.root);
return followReferences(browser);
};
const followReferences = (document) => document._value instanceof Reference
? get(document._value.href, document)
: document;
const followReferences = (browser) => jrefTypeOf(value(browser)) === "reference"
? get(value(browser).href, browser)
: browser;
export const value = (document) => document._value;
export const value = (browser) => browser._value;
export const step = curry((key, document) => {
export const typeOf = (browser) => jrefTypeOf(browser._value);
export const has = (key, browser) => key in browser._value;
export const length = (browser) => browser._value.length;
export const step = curry((key, browser) => {
return followReferences({
...document,
cursor: pointerAppend(`${key}`, document.cursor),
_value: document._value[key]
...browser,
cursor: pointerAppend(`${key}`, browser.cursor),
_value: browser._value[key]
});
});
export const iter = async function* (document) {
for (let index = 0; index < value(document).length; index++) {
yield step(index, document);
export const iter = async function* (browser) {
for (let index = 0; index < value(browser).length; index++) {
yield step(index, browser);
}
};
export const keys = function* (document) {
for (const key in value(document)) {
export const keys = function* (browser) {
for (const key in value(browser)) {
yield key;

@@ -62,11 +70,11 @@ }

export const values = async function* (document) {
for (const key in value(document)) {
yield step(key, document);
export const values = async function* (browser) {
for (const key in value(browser)) {
yield step(key, browser);
}
};
export const entries = async function* (document) {
for (const key in value(document)) {
yield [key, await step(key, document)];
export const entries = async function* (browser) {
for (const key in value(browser)) {
yield [key, await step(key, browser)];
}

@@ -73,0 +81,0 @@ };

@@ -15,2 +15,5 @@ import { addMediaTypePlugin } from "./media-types/media-types.js";

value,
typeOf,
has,
length,
step,

@@ -27,3 +30,5 @@ iter,

removeMediaTypePlugin,
setMediaTypeQuality
setMediaTypeQuality,
UnsupportedMediaTypeError,
UnknownMediaTypeError
} from "./media-types/media-types.js";

@@ -34,3 +39,4 @@

removeUriSchemePlugin,
retrieve
retrieve,
UnsupportedUriSchemeError
} from "./uri-schemes/uri-schemes.js";
import type { Response } from "undici";
import type { JRef } from "./jref/index.js";
import type { JRef, JRefType } from "./jref/index.js";
// Browser
export type Browser<T extends Document = Document> = {
uri: string;
document: T;
cursor: string;
};
export type Document = {
baseUri: string;
cursor: string;
root: JRef;
anchorLocation: (anchor: string | undefined) => string;
embedded?: Record<string, Document>;
};
export const get: (uri: string, document?: Document) => Promise<Document>;
export const value: (document: Document) => unknown;
export const step: (key: string, document: Document) => Promise<Document>;
export const iter: (document: Document) => AsyncGenerator<Document>;
export const keys: (document: Document) => Generator<string>;
export const values: (document: Document) => AsyncGenerator<string>;
export const entries: (document: Document) => AsyncGenerator<[string, Document]>;
export const get: <T extends Document>(uri: string, browser?: Browser) => Promise<Browser<T>>;
export const value: <T>(browser: Browser) => T;
export const typeOf: (browser: Browser) => JRefType;
export const has: (key: string, browser: Browser) => boolean;
export const length: (browser: Browser) => number;
export const step: (key: string, browser: Browser) => Promise<Browser>;
export const iter: (browser: Browser) => AsyncGenerator<Browser>;
export const keys: (browser: Browser) => Generator<string>;
export const values: (browser: Browser) => AsyncGenerator<Browser>;
export const entries: (browser: Browser) => AsyncGenerator<[string, Browser]>;
export class RetrievalError extends Error {
public constructor(message: string, cause: Error);
public get cause(): Error;
}
// Media Types
export type MediaTypePlugin = {
parse: (response: Response, fragment: string) => Promise<Document>;
export type MediaTypePlugin<T extends Document = Document> = {
parse: (response: Response) => Promise<T>;
fileMatcher: (path: string) => Promise<boolean>;

@@ -35,2 +46,11 @@ quality?: number;

export class UnsupportedMediaTypeError extends Error {
public constructor(mediaType: string, message?: string);
public get mediaType(): string;
}
export class UnknownMediaTypeError extends Error {
public constructor(message?: string);
}
// URI Schemes

@@ -41,7 +61,9 @@ export type UriSchemePlugin = {

export const retrieve: (uri: string, baseUri?: string) => Promise<Response>;
export const addUriSchemePlugin: (scheme: string, plugin: UriSchemePlugin) => void;
export const removeUriSchemePlugin: (scheme: string) => void;
export const retrieve: (uri: string, document?: Document) => Promise<{
response: Response;
fragment: string;
}>;
export class UnsupportedUriSchemeError extends Error {
public constructor(scheme: string, message?: string);
public get scheme(): string;
}

@@ -17,2 +17,5 @@ import { addMediaTypePlugin } from "./media-types/media-types.js";

value,
typeOf,
has,
length,
step,

@@ -29,3 +32,5 @@ iter,

removeMediaTypePlugin,
setMediaTypeQuality
setMediaTypeQuality,
UnsupportedMediaTypeError,
UnknownMediaTypeError
} from "./media-types/media-types.js";

@@ -36,3 +41,4 @@

removeUriSchemePlugin,
retrieve
retrieve,
UnsupportedUriSchemeError
} from "./uri-schemes/uri-schemes.js";

@@ -7,3 +7,3 @@ export type JRef = null | boolean | string | number | Reference | JRefObject | JRef[];

export const parse: (jref: string, reviver?: Reviver) => JRef;
export type Reviver = (key: string, value: unknown) => unknown;
export type Reviver = (key: string, value: JRef) => JRef | undefined;

@@ -13,2 +13,5 @@ export const stringify: (value: JRef, replacer?: (string | number)[] | null | Replacer, space?: string | number) => string;

export type JRefType = "object" | "array" | "string" | "number" | "boolean" | "null" | "reference" | "undefined";
export const jrefTypeOf: (value: unknown) => JRefType;
export class Reference {

@@ -15,0 +18,0 @@ constructor(href: string, value?: unknown);

export const parse = (jref, reviver = undefined) => {
return JSON.parse(jref, (key, value) => {
const newValue = value !== null && typeof value.$href === "string" ? new Reference(value.$href) : value;
const newValue = value !== null && typeof value.$ref === "string" ? new Reference(value.$ref) : value;

@@ -17,3 +17,3 @@ return reviver ? reviver(key, newValue) : newValue;

this.#href = href;
this.#value = value ?? { $href: href };
this.#value = value ?? { $ref: href };
}

@@ -29,1 +29,28 @@

}
export const jrefTypeOf = (value) => {
const jsType = typeof value;
switch (jsType) {
case "bigint":
return "number";
case "number":
case "string":
case "boolean":
case "undefined":
return jsType;
case "object":
if (value instanceof Reference) {
return "reference";
} else if (Array.isArray(value)) {
return "array";
} else if (value === null) {
return "null";
} else if (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null) {
return "object";
}
default:
const type = jsType === "object" ? Object.getPrototypeOf(value).constructor.name || "anonymous" : jsType;
throw Error(`Not a JRef compatible type: ${type}`);
}
};

@@ -5,7 +5,7 @@ import { parse } from "../jref/index.js";

export const jrefMediaTypePlugin = {
parse: async (response, fragment) => {
parse: async (response) => {
return {
baseUri: response.url,
cursor: fragment,
root: parse(await response.text())
root: parse(await response.text()),
anchorLocation: anchorLocation
};

@@ -15,1 +15,3 @@ },

};
const anchorLocation = (fragment) => decodeURI(fragment || "");

@@ -18,3 +18,3 @@ import { parse as parseContentType } from "content-type";

export const parseResponse = (response, fragment) => {
export const parseResponse = (response) => {
const contentTypeText = response.headers.get("content-type");

@@ -32,8 +32,8 @@ if (contentTypeText === null) {

return mediaTypePlugins[contentType.type].parse(response, fragment);
return mediaTypePlugins[contentType.type].parse(response);
};
export const getFileMediaType = (path) => {
export const getFileMediaType = async (path) => {
for (const contentType in mediaTypePlugins) {
if (mediaTypePlugins[contentType].fileMatcher(path)) {
if (await mediaTypePlugins[contentType].fileMatcher(path)) {
return contentType;

@@ -68,3 +68,3 @@ }

class UnsupportedMediaTypeError extends Error {
export class UnsupportedMediaTypeError extends Error {
constructor(mediaType, message = undefined) {

@@ -77,3 +77,3 @@ super(message);

class UnknownMediaTypeError extends Error {
export class UnknownMediaTypeError extends Error {
constructor(message = undefined) {

@@ -80,0 +80,0 @@ super(message);

import { createReadStream } from "node:fs";
import { readlink, lstat } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { parseIri, parseIriReference, toAbsoluteIri } from "@hyperjump/uri";
import { parseIri, toAbsoluteIri } from "@hyperjump/uri";
import { getFileMediaType } from "../media-types/media-types.js";
const retrieve = async (uri, document) => {
if (document) {
const { scheme } = parseIri(document.baseUri);
const retrieve = async (uri, baseUri) => {
const { scheme } = parseIri(baseUri);
if (baseUri) {
if (scheme !== "file") {
throw Error(`Accessing a file (${uri}) from a non-filesystem document (${document.baseUri}) is not allowed`);
throw Error(`Accessing a file (${uri}) from a non-filesystem document (${baseUri}) is not allowed`);
}
}
const { fragment } = parseIriReference(uri);
let responseUri = toAbsoluteIri(uri);

@@ -31,5 +31,5 @@

return { response, fragment: fragment ?? "" };
return response;
};
export const fileSchemePlugin = { retrieve };

@@ -1,2 +0,1 @@

import { parseIriReference } from "@hyperjump/uri";
import { acceptableMediaTypes } from "../media-types/media-types.js";

@@ -8,4 +7,2 @@

const retrieve = async (uri) => {
const { fragment } = parseIriReference(uri);
const response = await fetch(uri, { headers: { Accept: acceptableMediaTypes() } });

@@ -21,3 +18,3 @@

return { response, fragment: fragment ?? "" };
return response;
};

@@ -24,0 +21,0 @@

@@ -1,2 +0,2 @@

import { parseIriReference } from "@hyperjump/uri";
import { parseIriReference, resolveIri } from "@hyperjump/uri";

@@ -14,3 +14,4 @@

export const retrieve = (uri, document) => {
export const retrieve = (uri, baseUri) => {
uri = resolveIri(uri, baseUri);
const { scheme } = parseIriReference(uri);

@@ -22,6 +23,6 @@

return uriSchemePlugins[scheme].retrieve(uri, document);
return uriSchemePlugins[scheme].retrieve(uri, baseUri);
};
class UnsupportedUriSchemeError extends Error {
export class UnsupportedUriSchemeError extends Error {
constructor(scheme, message = undefined) {

@@ -28,0 +29,0 @@ super(message);

{
"name": "@hyperjump/browser",
"version": "1.0.0",
"version": "1.1.0",
"description": "Browse JSON-compatible data with hypermedia references",

@@ -18,3 +18,3 @@ "type": "module",

"lint": "eslint lib",
"test": "mocha 'lib/**/*.spec.ts'"
"test": "vitest --watch=false"
},

@@ -29,3 +29,4 @@ "repository": {

"jref",
"hypermedia"
"hypermedia",
"$ref"
],

@@ -39,15 +40,13 @@ "author": "Jason Desrosiers <jdesrosi@gmail.com>",

"devDependencies": {
"@types/chai": "*",
"@types/mocha": "*",
"@types/node": "*",
"@typescript-eslint/eslint-plugin": "*",
"@typescript-eslint/parser": "*",
"chai": "*",
"eslint": "*",
"eslint-import-resolver-exports": "*",
"eslint-import-resolver-node": "*",
"eslint-import-resolver-typescript": "*",
"eslint-plugin-import": "*",
"mocha": "*",
"ts-node": "*",
"typescript": "*",
"yaml": "*"
"undici": "*",
"vitest": "*"
},

@@ -61,5 +60,4 @@ "engines": {

"content-type": "^1.0.5",
"just-curry-it": "^5.3.0",
"undici": "^5.23.0"
"just-curry-it": "^5.3.0"
}
}

@@ -19,18 +19,2 @@ # Hyperjump - Browser

### Browser
When in a browser context, this library is designed to use the browser's `fetch`
implementation instead of a node.js fetch clone. The Webpack bundler does this
properly without any extra configuration, but if you are using the Rollup
bundler you will need to include the `browser: true` option in your Rollup
configuration.
```javascript
plugins: [
resolve({
browser: true
})
]
```
## JRef Browser

@@ -70,3 +54,3 @@

* get(uri: string, document?: Document): Promise\<Document>
* get(uri: string, browser?: Browser): Promise\<Browser>

@@ -78,21 +62,32 @@ Retrieve a document located at the given URI. Support for [JRef] is built

how to support other URI schemes.
* value(document: Document) => Json
* value(browser: Browser) => JRef
Get the JSON compatible value the document represents. Any references will
have been followed so you'll never receive a `Reference` type.
* step(key: string | number, document: Document) => Promise\<Document>
Get the JRef compatible value the document represents.
* typeOf(browser: Browser) => JRefType
Move the document cursor by the given "key" value. This is analogous to
Works the same as the `typeof` keyword. It will return one of the JSON types
(null, boolean, number, string, array, object) or "reference". If the value
is not one of these types, it will throw an error.
* has(key: string, browser: Browser) => boolean
Returns whether or not a property is present in the object that the browser
represents.
* length(browser: Browser) => number
Get the length of the array that the browser represents.
* step(key: string | number, browser: Browser) => Promise\<Browser>
Move the browser cursor by the given "key" value. This is analogous to
indexing into an object or array (`foo[key]`). This function supports
curried application.
* **Schema.iter**: (document: Document) => AsyncGenerator\<Document>
* iter(browser: Browser) => AsyncGenerator\<Browser>
Iterate over the items in the array that the Document represents.
* **Schema.entries**: (document: Document) => AsyncGenerator\<[string, Document]>
Iterate over the items in the array that the document represents.
* entries(browser: Browser) => AsyncGenerator\<[string, Browser]>
Similar to `Object.entries`, but yields Documents for values.
* **Schema.values**: (document: Document) => AsyncGenerator\<Document>
Similar to `Object.entries`, but yields Browsers for values.
* values(browser: Browser) => AsyncGenerator\<Browser>
Similar to `Object.values`, but yields Documents for values.
* **Schema.keys**: (document: Document) => Generator\<string>
Similar to `Object.values`, but yields Browsers for values.
* keys(browser: Browser) => Generator\<string>

@@ -115,9 +110,12 @@ Similar to `Object.keys`.

return {
documentValue: YAML.parse(await response.text(), (key, value) => {
return value !== null && typeof value.$href === "string"
? new Reference(value.$href)
baseUri: response.url,
root: (response) => YAML.parse(await response.text(), (key, value) => {
return value !== null && typeof value.$ref === "string"
? new Reference(value.$ref)
: value;
});
},
anchorLocation: (fragment) => decodeUri(fragment ?? "");
};
}
},
fileMatcher: (path) => path.endsWith(".jref")
});

@@ -139,3 +137,3 @@

* type MediaTypePlugin
* parse: (content: string) => Document
* parse: (response: Response) => Document
* [quality](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values):

@@ -165,3 +163,3 @@ number (defaults to `1`)

addUriSchemePlugin("urn", {
parse: (urn, document) => {
parse: (urn, baseUri) => {
let { nid, nss, query, fragment } = parseUrn(urn);

@@ -178,3 +176,3 @@ nid = nid.toLowerCase();

return retrieve(uri, document);
return retrieve(uri, baseUri);
}

@@ -195,7 +193,7 @@ });

* type UriSchemePlugin
* retrieve: (uri: string, document: Document) => Promise\<{ response: Response, fragment: string }>
* retrieve: (uri: string, baseUri?: string) => Promise\<Response>
* removeUriSchemePlugin(scheme: string): void
Remove support for a URI scheme.
* retrieve(uri: string, document: Document) => Promise\<{ response: Response, fragment: string }>
* retrieve(uri: string, baseUri?: string) => Promise\<Response>

@@ -207,15 +205,15 @@ This is used internally, but you may need it if mapping names to locators

Parse and stringify [JRef] values using the same API as the `JSON` built-in
functions including reviver and replacer functions.
`parse` and `stringify` [JRef] values using the same API as the `JSON` built-in
functions including `reviver` and `replacer` functions.
```javascript
import { parse, stringify, Reference } from "@hyperjump/browser/jref";
import { parse, stringify, jrefTypeOf } from "@hyperjump/browser/jref";
const blogPostJref = `{
"title": "Working with JRef",
"author": { "$href": "/author/jdesrosiers" },
"author": { "$ref": "/author/jdesrosiers" },
"content": "lorem ipsum dolor sit amet",
}`;
const blogPost = parse(blogPostJref);
blogPost.author instanceof Reference; // => true
jrefTypeOf(blogPost.author) // => "reference"
blogPost.author.href; // => "/author/jdesrosiers"

@@ -226,2 +224,15 @@

### API
export type Replacer = (key: string, value: unknown) => unknown;
* parse: (jref: string, reviver?: (key: string, value: unknown) => unknown) => JRef;
Same as `JSON.parse`, but converts `{ "$ref": "..." }` to `Reference`
objects.
* stringify: (value: JRef, replacer?: (string | number)[] | null | Replacer, space?: string | number) => string;
Same as `JSON.stringify`, but converts `Reference` objects to `{ "$ref":
"... " }`
* jrefTypeOf: (value: unknown) => "object" | "array" | "string" | "number" | "boolean" | "null" | "reference" | "undefined";
## Contributing

@@ -228,0 +239,0 @@

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