Repository: jackfranklin/remote-data-js
Branch: master
Commit: 5f0f8445c3d5
Files: 15
Total size: 19.8 KB
Directory structure:
gitextract_8910mnw4/
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── __tests__/
│ ├── from-promise-test.js
│ └── remote-data-test.js
├── package.json
├── prettier.config.js
└── src/
├── index.js
├── request.js
└── states.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["es2015"],
"plugins": ["transform-object-rest-spread"]
}
================================================
FILE: .eslintrc.js
================================================
module.exports = {
extends: ['unobtrusive', 'prettier'],
plugins: ['prettier'],
parser: 'babel-eslint',
env: {
browser: true,
jest: true,
es6: true,
},
rules: {
'prettier/prettier': 'error',
},
}
================================================
FILE: .gitignore
================================================
lib/
================================================
FILE: .npmignore
================================================
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "8"
================================================
FILE: CHANGELOG.md
================================================
### 0.2.1 - 5th Sept 2016
- ensure `onChange` is kept when a new instance is created
### 0.2.0 - 5th Sept 2016
- `case` statement support
- tests refactored to use Jest
### 0.1.0 - 11th June 2016
- Initial "beta" release
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) 2016 Jack Franklin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# RemoteData.js
Inspired by Kris Jenkins'
[RemoteData](http://package.elm-lang.org/packages/krisajenkins/elm-exts/25.1.0/Exts-RemoteData)
Elm package, this library provides an object for representing remote data in
your application.
[](https://travis-ci.org/jackfranklin/remote-data-js)
```
npm install --save remote-data-js
```
## Motivations
By representing the data and the state of the request in one object it becomes
impossible for you to have data that's out of sync.
A typical app might model the data as:
```
{ loading: true, data: undefined }
```
And then update the values when the request succeeds. However, this really is
one piece of information that is now represented across two keys, and as such it
can become out of sync.
Instead, `RemoteData` models both the _request_ and the _data_ in one object, so
they can never be out of sync with each other.
A `RemoteData` instance has one of four states:
* `NOT_ASKED` - you've got started the request yet
* `PENDING` - the request is in flight
* `SUCCESS` - we have data from the request
* `FAILURE` - the request went wrong, we have an error for it
You can check the status of a `RemoteData` instance and therefore represent data
in your application accordingly.
Additionally, `RemoteData` instances are _never_ mutated, but pass a new version
of themselves through callbacks. This means any mutation bugs with rendering off
your remote data instances are not a concern, and that this library can play
nicely with React, Redux and others.
## Example
```js
import RemoteData from 'remote-data'
const githubPerson = new RemoteData({
url: username => `https://api.github.com/users/${username}`,
onChange: remoteData => console.log('State changed!', remoteData),
})
// then later on
githubPerson
.fetch('jackfranklin')
.then(remoteData => {
// it worked fine
console.log(remoteData.isSuccess()) // true
console.log(remoteData.data) // github api data
console.log(remoteData.response.status) // 200
})
.catch(remoteData => {
// something went wrong
console.log(remoteData.isSuccess()) // false
console.log(remoteData.isFailure()) // true
console.log(remoteData.data) // error info
console.log(remoteData.response.status) // response status code
})
```
## API
### Creating `RemoteData` instances
The configuration you can provide when creating a new instance of RemoteData are
as follows:
```js
const instance = new RemoteData({
url: (name) => `https://api.github.com/users/${username}`
onChange: (newInstance) => {...},
parse: (response) => response.json,
fetchOptions: {}
});
```
These are fully documented below:
* `url: String | Function`: if given a string, this will be the URL that the
request is made to. If it's a function it will be called when `fetch` is
called, passing any arguments through. For example, `remoteData.fetch('jack')`
will call the `url` function, passing `jack` as the argument.
* `onChange: Function`: a function called whenever the state of a remote data
instance changes. This is passed in the new RemoteData instance.
* `parse: Function`: a function used to parse the `Response` from the HTTP
request. Defaults to `response.json()`.
* `fetchOptions: Object`: an object that is passed through to `fetch` and allows
you to configure headers and any other request options.
### Making Requests
To make a request, call `fetch` on the `RemoteData` instance:
```js
const githubPerson = new RemoteData({
url: name => `https://api.github.com/users/${username}`,
onChange: newGithubPerson => console.log(newGithubPerson),
})
githubPerson.fetch('jackfranklin')
```
A promise is returned and the value it will resolve to is the new `RemoteData`
instance:
```js
githubPerson.fetch('jackfranklin').then(newData => {
console.log(newData.data) // GitHub API data, parsed from JSON
console.log(newData.response.status) // status code
console.log(newData.state) // 'SUCCESS'
})
```
### Checking the status of a request
You can call any of the following methods:
* `isFinished()` : true if a request has succeeded or failed.
* `isNotAsked()` : true if the request hasn't been asked for (this is the
default state).
* `isPending()` : true if the request has been started but is pending
* `isFailure()` : true if the request has failed
* `isSuccess()` : true if the request has succeeded
You can "switch" on a RemoteData instance's state similarly to functional
languages and the JavaScript
[Union Type](https://www.npmjs.com/package/union-type) package:
```js
githubPerson.fetch('jackfranklin').then(data => {
const message = data.case({
NotAsked: () => 'Initializing...',
Pending: () => 'Loading...',
Success: data => renderData(data),
Failure: error => renderError(error),
})
})
```
If you don't handle all four possible states, you must include a default handler
named `_` (underscore):
```js
githubPerson.fetch('jackfranklin').then(data => {
const message = data.case({
Success: data => renderData(data),
Failure: error => renderError(error),
_: () => 'Loading...',
})
})
```
You can call `.data` on a request to access the data, but be aware that this
_will throw an error_ if the request hasn't been asked for or is pending.
You can call `.response` on a request to access the response, but be aware that
this _will throw an error_ if the request hasn't been asked for or is pending.
## Making remote data instances from a promise.
Let's say you have your own custom API library in your app for making API
requests that returns promises. In this instance, you don't want to use
RemoteData's own `fetch` based API to initiate the request, but instead you want
to wrap your promise in a `RemoteData` instance:
```js
import { fromPromise } from 'remote-data-js'
import myCustomApiRequestLib from 'my-custom-lib'
const onChange = newRemoteData => {...}
const apiRequest = myCustomApiRequestLib('/foo')
const remoteDataInstance = fromPromise(apiRequest, { onChange })
remoteDataInstance.isPending() // => true
```
================================================
FILE: __tests__/from-promise-test.js
================================================
import { fromPromise } from '../src/index'
it('can be constructed from a promise and be in the loading state', () => {
const onChange = () => {}
const prom = new Promise(resolve => resolve({ success: true }))
const remoteData = fromPromise(prom, { onChange })
expect(remoteData.isPending()).toBe(true)
})
it('gives the data back when it succeeds', done => {
const onChange = res => {
expect(res.isSuccess()).toEqual(true)
expect(res.data).toEqual({ success: true })
done()
}
const prom = new Promise(resolve => resolve({ success: true }))
const remoteData = fromPromise(prom, { onChange })
expect(remoteData.isPending()).toBe(true)
})
it('gives the error back when it fails', done => {
const onChange = res => {
expect(res.isFailure()).toEqual(true)
expect(res.data).toEqual({ error: true })
done()
}
/* eslint-disable prefer-promise-reject-errors */
const prom = new Promise((resolve, reject) => reject({ error: true }))
/* eslint-enable prefer-promise-reject-errors */
fromPromise(prom, { onChange })
})
================================================
FILE: __tests__/remote-data-test.js
================================================
import RemoteData from '../src/index'
import fetchMock from 'fetch-mock'
const mockSuccess = url => fetchMock.mock(url, { success: true })
const mockError = url => fetchMock.mock(url, 404)
const resetAndMockSuccess = url => {
fetchMock.restore()
mockSuccess(url)
}
const resetAndMockError = url => {
fetchMock.restore()
mockError(url)
}
const resetAndMockWithResponse = (url, response) => {
fetchMock.restore()
fetchMock.mock(url, response)
}
const makeInstance = (obj = {}) => {
const args = Object.assign(
{},
{
url: 'api.com/1',
},
obj
)
return new RemoteData(args)
}
it('Is NOT_ASKED by default', () => {
expect(new RemoteData().isNotAsked()).toBe(true)
})
describe('defining the URL', () => {
it('uses a url if it is given one', () => {
resetAndMockSuccess('api.com/1')
const instance = makeInstance({
url: x => `api.com/${x}`,
})
return instance.fetch('1').then(() => {
expect(fetchMock.called('api.com/1')).toBe(true)
})
})
it('uses a string if given a string', () => {
resetAndMockSuccess('api.com/1')
const instance = makeInstance()
return instance.fetch('1').then(() => {
expect(fetchMock.called('api.com/1')).toBe(true)
})
})
})
it('calls onChange twice when there is a successful request', () => {
resetAndMockSuccess('api.com/1')
const onChange = jest.fn()
const instance = makeInstance({ onChange })
return instance.fetch().then(() => {
expect(onChange.mock.calls[0][0].isPending()).toBe(true)
expect(onChange.mock.calls[1][0].isSuccess()).toBe(true)
})
})
it('calls onChange twice for a failed request', () => {
resetAndMockError('api.com/1')
const onChange = jest.fn()
const instance = makeInstance({ onChange })
return instance.fetch().then(() => {
expect(onChange.mock.calls[0][0].isPending()).toBe(true)
expect(onChange.mock.calls[1][0].isFailure()).toBe(true)
})
})
it('parses as JSON by default', () => {
resetAndMockWithResponse('api.com/1', { some: 'json' })
const instance = makeInstance()
return instance.fetch().then(result => {
expect(result.data).toEqual({ some: 'json' })
})
})
it('allows a custom parser function to be provided', () => {
resetAndMockWithResponse('api.com/1', 'Hello World')
const instance = makeInstance({ parse: x => x.text() })
return instance.fetch().then(result => {
expect(result.data).toEqual('Hello World')
})
})
it('provides `response` to allow access to the raw HTTP response', () => {
resetAndMockWithResponse('api.com/1', { some: 'json' })
const instance = makeInstance()
return instance.fetch().then(result => {
expect(result.data).toEqual({ some: 'json' })
expect(result.response.status).toEqual(200)
})
})
it('throws if you access the response when the request is not finished', () => {
const instance = makeInstance()
expect(() => instance.response).toThrowError(
/Cannot get response for request that hasn't finished/
)
})
it('throws if you try to access the data before it has been fetched', () => {
const instance = makeInstance()
expect(() => instance.data).toThrowError(
/Cannot get data for request that hasn't finished/
)
})
describe('the case method', () => {
it('first calls the NotAsked callback', () => {
const instance = makeInstance()
const notAsked = jest.fn()
const otherFn = jest.fn()
instance.case({
NotAsked: notAsked,
Pending: otherFn,
Failure: otherFn,
Success: otherFn,
})
expect(notAsked).toBeCalled()
expect(otherFn).not.toBeCalled()
})
it('calls the success callback when data has been fetched', () => {
resetAndMockSuccess('api.com/1')
const instance = makeInstance()
const successFn = jest.fn()
const otherFn = jest.fn()
return instance.fetch().then(result => {
result.case({
NotAsked: otherFn,
Pending: otherFn,
Failure: otherFn,
Success: successFn,
})
expect(successFn).toBeCalledWith({ success: true })
expect(otherFn).not.toBeCalled()
})
})
it('calls the pending callback when the request is in motion', () => {
resetAndMockSuccess('api.com/1')
const pendingFn = jest.fn()
const otherFn = jest.fn()
let count = 0
const instance = new RemoteData({
url: 'api.com/1',
onChange(remoteData) {
if (count++ > 0) return
remoteData.case({
NotAsked: otherFn,
Pending: pendingFn,
Failure: otherFn,
Success: otherFn,
})
expect(pendingFn).toBeCalled()
expect(otherFn).not.toBeCalled()
},
})
return instance.fetch()
})
it('calls the failure callback when the request failed', () => {
resetAndMockError('api.com/1')
const instance = makeInstance()
const failedFn = jest.fn()
const otherFn = jest.fn()
return instance.fetch().then(result => {
result.case({
NotAsked: otherFn,
Pending: otherFn,
Failure: failedFn,
Success: otherFn,
})
expect(failedFn).toBeCalled()
expect(otherFn).not.toBeCalled()
})
})
it('will call the default callback if none match', () => {
resetAndMockError('api.com/1')
const handler = jest.fn()
const instance = makeInstance()
instance.case({
_: handler,
})
expect(handler).toBeCalled()
})
it('copies the onChange event over when a new remote data instance is created', () => {
const onChange = jest.fn()
const instance = makeInstance({
onChange,
})
resetAndMockSuccess('api.com/1')
return instance.fetch().then(newInstance => {
expect(onChange.mock.calls.length).toBe(2)
expect(newInstance.onChange).toBe(onChange)
})
})
})
================================================
FILE: package.json
================================================
{
"name": "remote-data-js",
"version": "0.2.1",
"description": "",
"main": "lib/index.js",
"scripts": {
"build-cjs": "babel src -d lib",
"prepack": "npm run build-cjs",
"jest": "jest",
"test": "npm run lint && npm run jest",
"watch": "npm run jest --watch",
"lint": "eslint src/*.js __tests__/*.js",
"lint-fix": "npm run lint -- --fix"
},
"keywords": [],
"author": "Jack Franklin",
"license": "MIT",
"devDependencies": {
"babel-cli": "6.9.0",
"babel-core": "^6.6.0",
"babel-eslint": "^8.0.3",
"babel-jest": "^22.0.1",
"babel-plugin-transform-object-rest-spread": "6.8.0",
"babel-polyfill": "6.13.0",
"babel-preset-es2015": "^6.6.0",
"babel-register": "6.9.0",
"eslint": "^4.11.0",
"eslint-config-prettier": "^2.7.0",
"eslint-config-standard": "^10.2.1",
"eslint-config-unobtrusive": "^1.2.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-prettier": "^2.4.0",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"fetch-mock": "4.5.4",
"jest": "^21.2.1",
"prettier": "^1.8.2",
"webpack-notifier": "1.3.0"
}
}
================================================
FILE: prettier.config.js
================================================
module.exports = {
semi: false,
trailingComma: 'es5',
singleQuote: true,
proseWrap: 'always',
}
================================================
FILE: src/index.js
================================================
import { NOT_ASKED, PENDING, FAILURE, SUCCESS } from './states'
import { makeFetchRequest } from './request'
class RemoteData {
constructor({
url,
state = NOT_ASKED,
onChange = () => {},
parse = x => x.json(),
fetchOptions = {},
rawResponse,
stateData,
} = {}) {
this.state = state
this.url = url
this.onChange = onChange
this.parse = parse
this.fetchOptions = fetchOptions
this.stateData = stateData
this.rawResponse = rawResponse
}
config() {
const keys = [
'onSuccess',
'onFailure',
'parse',
'fetchOptions',
'state',
'url',
'onChange',
]
const config = {}
keys.forEach(k => {
config[k] = this[k]
})
return config
}
// the default implementations here call through to the _ function
// which is called if the user does not provide a function
/* eslint-disable no-use-before-define */
case({
NotAsked = () => _(),
Pending = () => _(),
Failure = (...args) => _(...args),
Success = (...args) => _(...args),
_ = () => {},
}) {
/* eslint-enable no-use-before-define */
switch (this.state) {
case NOT_ASKED:
return NotAsked()
case PENDING:
return Pending()
case FAILURE:
return Failure(this.stateData)
case SUCCESS:
return Success(this.stateData)
}
}
isFinished() {
return this.isFailure() || this.isSuccess()
}
isPending() {
return this.state === PENDING
}
isNotAsked() {
return this.state === NOT_ASKED
}
isFailure() {
return this.state === FAILURE
}
isSuccess() {
return this.state === SUCCESS
}
get data() {
if (this.isFinished()) {
return this.stateData
} else {
throw new Error("Cannot get data for request that hasn't finished")
}
}
get response() {
if (this.isFinished()) {
return this.rawResponse
} else {
throw new Error("Cannot get response for request that hasn't finished")
}
}
makeNewAndOnChange(opts = {}) {
const newRemoteData = new this.constructor({
...this.config(),
...opts,
})
this.onChange(newRemoteData)
return newRemoteData
}
fetch(...args) {
const reqUrl = typeof this.url === 'function' ? this.url(...args) : this.url
this.makeNewAndOnChange({ state: PENDING })
return makeFetchRequest(this, reqUrl)
}
}
export const fromPromise = (promise, { onChange = () => {} } = {}) => {
const instance = new RemoteData({ onChange, state: PENDING })
promise.then(
result => {
return instance.makeNewAndOnChange({
state: SUCCESS,
stateData: result,
})
},
error => {
return instance.makeNewAndOnChange({
state: FAILURE,
stateData: error,
})
}
)
return instance
}
export default RemoteData
================================================
FILE: src/request.js
================================================
import { FAILURE, SUCCESS } from './states'
const checkStatus = response => {
if (response.status >= 200 && response.status < 300) {
return response
} else {
const error = new Error(response.statusText)
error.response = response
throw error
}
}
const dataResponse = (remoteDataInstance, rawResponse, data, isError) => {
return remoteDataInstance.makeNewAndOnChange({
state: isError ? FAILURE : SUCCESS,
stateData: data,
rawResponse,
})
}
const successfulResponse = (remoteDataInstance, rawResponse, data) => {
return dataResponse(remoteDataInstance, rawResponse, data, false)
}
const failureResponse = (remoteDataInstance, rawResponse, data) => {
return dataResponse(remoteDataInstance, rawResponse, data, true)
}
const parseAndKeepResponse = parseFn => result => {
return Promise.all([
Promise.resolve(result),
Promise.resolve(parseFn(result)),
])
}
export const makeFetchRequest = (remoteDataInstance, url) => {
return fetch(url, remoteDataInstance.fetchOptions)
.then(checkStatus)
.then(parseAndKeepResponse(remoteDataInstance.parse))
.then(([rawResponse, data]) =>
successfulResponse(remoteDataInstance, rawResponse, data)
)
.catch(error => failureResponse(remoteDataInstance, error.response, error))
}
================================================
FILE: src/states.js
================================================
export const NOT_ASKED = 'REMOTE_DATA_NOT_ASKED'
export const PENDING = 'REMOTE_DATA_PENDING'
export const FAILURE = 'REMOTE_DATA_FAILURE'
export const SUCCESS = 'REMOTE_DATA_SUCCESS'
gitextract_8910mnw4/
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── __tests__/
│ ├── from-promise-test.js
│ └── remote-data-test.js
├── package.json
├── prettier.config.js
└── src/
├── index.js
├── request.js
└── states.js
SYMBOL INDEX (18 symbols across 3 files)
FILE: __tests__/remote-data-test.js
method onChange (line 174) | onChange(remoteData) {
FILE: src/index.js
class RemoteData (line 5) | class RemoteData {
method constructor (line 6) | constructor({
method config (line 24) | config() {
method case (line 44) | case({
method isFinished (line 64) | isFinished() {
method isPending (line 68) | isPending() {
method isNotAsked (line 72) | isNotAsked() {
method isFailure (line 76) | isFailure() {
method isSuccess (line 80) | isSuccess() {
method data (line 84) | get data() {
method response (line 92) | get response() {
method makeNewAndOnChange (line 100) | makeNewAndOnChange(opts = {}) {
method fetch (line 110) | fetch(...args) {
FILE: src/states.js
constant NOT_ASKED (line 1) | const NOT_ASKED = 'REMOTE_DATA_NOT_ASKED'
constant PENDING (line 2) | const PENDING = 'REMOTE_DATA_PENDING'
constant FAILURE (line 3) | const FAILURE = 'REMOTE_DATA_FAILURE'
constant SUCCESS (line 4) | const SUCCESS = 'REMOTE_DATA_SUCCESS'
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
{
"path": ".babelrc",
"chars": 75,
"preview": "{\n \"presets\": [\"es2015\"],\n \"plugins\": [\"transform-object-rest-spread\"]\n}\n"
},
{
"path": ".eslintrc.js",
"chars": 226,
"preview": "module.exports = {\n extends: ['unobtrusive', 'prettier'],\n plugins: ['prettier'],\n parser: 'babel-eslint',\n env: {\n "
},
{
"path": ".gitignore",
"chars": 5,
"preview": "lib/\n"
},
{
"path": ".npmignore",
"chars": 0,
"preview": ""
},
{
"path": ".travis.yml",
"chars": 35,
"preview": "language: node_js\nnode_js:\n - \"8\"\n"
},
{
"path": "CHANGELOG.md",
"chars": 223,
"preview": "### 0.2.1 - 5th Sept 2016\n- ensure `onChange` is kept when a new instance is created\n\n### 0.2.0 - 5th Sept 2016\n- `case`"
},
{
"path": "LICENSE.md",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Jack Franklin\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 6133,
"preview": "# RemoteData.js\n\nInspired by Kris Jenkins'\n[RemoteData](http://package.elm-lang.org/packages/krisajenkins/elm-exts/25.1."
},
{
"path": "__tests__/from-promise-test.js",
"chars": 1063,
"preview": "import { fromPromise } from '../src/index'\n\nit('can be constructed from a promise and be in the loading state', () => {\n"
},
{
"path": "__tests__/remote-data-test.js",
"chars": 5810,
"preview": "import RemoteData from '../src/index'\nimport fetchMock from 'fetch-mock'\n\nconst mockSuccess = url => fetchMock.mock(url,"
},
{
"path": "package.json",
"chars": 1196,
"preview": "{\n \"name\": \"remote-data-js\",\n \"version\": \"0.2.1\",\n \"description\": \"\",\n \"main\": \"lib/index.js\",\n \"scripts\": {\n \"b"
},
{
"path": "prettier.config.js",
"chars": 104,
"preview": "module.exports = {\n semi: false,\n trailingComma: 'es5',\n singleQuote: true,\n proseWrap: 'always',\n}\n"
},
{
"path": "src/index.js",
"chars": 2878,
"preview": "import { NOT_ASKED, PENDING, FAILURE, SUCCESS } from './states'\n\nimport { makeFetchRequest } from './request'\n\nclass Rem"
},
{
"path": "src/request.js",
"chars": 1295,
"preview": "import { FAILURE, SUCCESS } from './states'\n\nconst checkStatus = response => {\n if (response.status >= 200 && response."
},
{
"path": "src/states.js",
"chars": 184,
"preview": "export const NOT_ASKED = 'REMOTE_DATA_NOT_ASKED'\nexport const PENDING = 'REMOTE_DATA_PENDING'\nexport const FAILURE = 'RE"
}
]
About this extraction
This page contains the full source code of the jackfranklin/remote-data-js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (19.8 KB), approximately 5.5k tokens, and a symbol index with 18 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.