Skip to content

Commit 2b68c01

Browse files
init commit
0 parents  commit 2b68c01

39 files changed

Lines changed: 4556 additions & 0 deletions

.github/actions/setup/action.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Setup
2+
description: Install dependencies and build package
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Install Node.js
8+
uses: actions/setup-node@v4
9+
with:
10+
node-version: 'lts/*'
11+
check-latest: true
12+
registry-url: https://registry.npmjs.org/
13+
14+
- uses: pnpm/action-setup@v4
15+
name: Install pnpm
16+
with:
17+
run_install: false
18+
version: 10
19+
20+
- name: Install dependencies
21+
shell: bash
22+
run: pnpm install

.github/workflows/publish.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Publish
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: checkout
12+
uses: actions/checkout@v4
13+
14+
- name: setup
15+
uses: ./.github/actions/setup
16+
17+
- name: build
18+
run: pnpm run build
19+
20+
- name: publish to npm
21+
env:
22+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23+
NPM_CONFIG_PROVENANCE: true
24+
run: |
25+
pnpm publish \
26+
--access=public \

.github/workflows/test-pr.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Test PR
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: checkout
10+
uses: actions/checkout@v4
11+
with:
12+
fetch-depth: 0
13+
14+
- name: setup
15+
uses: ./.github/actions/setup
16+
17+
- name: test
18+
run: pnpm test
19+
lint-check:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: checkout
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
27+
- name: setup
28+
uses: ./.github/actions/setup
29+
30+
- name: test
31+
run: pnpm lint:check
32+
format-check:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: checkout
36+
uses: actions/checkout@v4
37+
with:
38+
fetch-depth: 0
39+
40+
- name: setup
41+
uses: ./.github/actions/setup
42+
43+
- name: test
44+
run: pnpm format:check

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# User preferences
2+
.DS_Store
3+
4+
# Dependencies
5+
node_modules/
6+
7+
# Builds / Caches
8+
dist/
9+
10+
11+
# IDEs / Editors
12+
.vscode/*
13+
!.vscode/extensions.json
14+
15+
16+
# Temporary files
17+
TODO

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
pnpm-lock.yaml

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 ComfortablyCoding
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# reso.js
2+
3+
**`reso.js`** is a `Node.js` client for interacting with RESO Web API services, fully aligned with the current [RESO Web API specification](https://github.com/RESOStandards/transport).
4+
5+
It includes features like custom request/response hooks, automatic authentication token refresh, and built-in rate limiting.
6+
7+
## Table of Contents
8+
9+
- [Installation](#installation)
10+
- [Compatibility](#compatibility)
11+
- [Usage](#usage)
12+
- [Getting Started](#getting-started)
13+
- [Bearer Token Authentication](#bearer-token-authentication)
14+
- [Client Credentials Authentication](#client-credentials-authentication)
15+
- [Querying the API](#querying-the-api)
16+
- [readByQuery](#readbyquery)
17+
- [readById](#readbyid)
18+
- [Fetching $metadata](#fetching-metadata)
19+
- [Rate Limiting](#rate-limiting)
20+
- [Hooks](#hooks)
21+
- [Error Handling](#error-handling)
22+
- [Contributing](#contributing)
23+
- [License](#license)
24+
25+
## Installation
26+
27+
Install with your preferred package manager:
28+
29+
```bash
30+
pnpm add reso.js
31+
# or
32+
npm install reso.js
33+
# or
34+
yarn add reso.js
35+
```
36+
37+
## Compatibility
38+
39+
This library works with any RESO-compliant server following the [RESO Web API spec](https://github.com/RESOStandards/transport). Additionally, it provides official support for:
40+
41+
- [Spark Platform](https://sparkplatform.com)
42+
- [Trestle](https://trestle.corelogic.com)
43+
- [Bridge Interactive](https://www.bridgeinteractive.com)
44+
- [MLS Grid](https://www.mlsgrid.com)
45+
46+
## Usage
47+
48+
### Getting Started
49+
50+
`reso.js` supports both **Bearer Token** and **Client Credentials** authentication strategies. Refer to your feed's documentation for your feeds supported strategy and how to obtain credentials.
51+
52+
#### Bearer Token Authentication
53+
54+
```js
55+
import { createFeed } from 'reso.js';
56+
57+
const feed = createFeed({
58+
http: {
59+
baseURL: 'http://my-reso-api.com',
60+
},
61+
auth: {
62+
type: 'token',
63+
credentials: {
64+
token: 'MY_TOKEN',
65+
},
66+
},
67+
});
68+
```
69+
70+
> The bearer token provided must not expire.
71+
72+
#### Client Credentials Authentication
73+
74+
```js
75+
import { createFeed } from 'reso.js';
76+
77+
const feed = createFeed({
78+
http: {
79+
baseURL: 'http://my-reso-api.com',
80+
},
81+
auth: {
82+
type: 'credentials',
83+
credentials :{
84+
tokenURL: 'http://my-reso-api.com/token'
85+
clientId: 'MY_CLIENT_ID'
86+
clientSecret: 'MY_CLIENT_SECRET'
87+
}
88+
},
89+
});
90+
```
91+
92+
You can optionally include `grantType` and `scope`. Tokens will automatically refresh 30 seconds before expiry by default.
93+
94+
To custom the refresh buffer it can be passed in the with the auth strategy:
95+
96+
```js
97+
import { createFeed } from 'reso.js';
98+
99+
const feed = createFeed({
100+
http: {
101+
baseURL: 'http://my-reso-api.com',
102+
},
103+
auth: {
104+
type: 'credentials',
105+
credentials :{
106+
tokenURL: 'http://my-reso-api.com/token'
107+
clientId: 'MY_CLIENT_ID'
108+
clientSecret: 'MY_CLIENT_SECRET'
109+
},
110+
refreshBuffer:40000,
111+
},
112+
});
113+
```
114+
115+
### Querying the API
116+
117+
#### `readByQuery`
118+
119+
Perform a RESO-compliant query. The function is an async generator that will auto paginate any next pages if the nextLink is present every time `.next()` is called or the result is looped over.
120+
121+
```js
122+
for await (let properties of feed.readByQuery('Property', '$filter=ListPrice gt 100000')) {
123+
for (property of properties.values) {
124+
console.log(property);
125+
}
126+
}
127+
```
128+
129+
#### `readById`
130+
131+
Retrieve a specific record by ID:
132+
133+
```js
134+
const myProperty = await feed.readyById('Property', 123);
135+
console.log(myProperty.value);
136+
```
137+
138+
### Fetching `$metadata`
139+
140+
Get the feed’s metadata XML:
141+
142+
```js
143+
const metadata = await feed.$metadata();
144+
console.log(metadata);
145+
```
146+
147+
### Rate Limiting
148+
149+
Rate limiting is built-in for supported providers. You can also customize limits:
150+
151+
```js
152+
const feed = createFeed({
153+
http: {
154+
baseURL: 'http://my-reso-api.com',
155+
},
156+
limiter: {
157+
duration: 30000, // 30 seconds
158+
points: 5, // max 5 requests per duration
159+
},
160+
});
161+
```
162+
163+
> `duration` defines the time window in milliseconds, and `points` sets the maximum number of requests allowed within that window.
164+
165+
### Hooks
166+
167+
The clients behaviour can be customized with the following supported hooks:
168+
169+
- `onRequest`
170+
- `onRequestError`
171+
- `onResponse`
172+
- `onResponseError`
173+
174+
These are backed by [ofetch interceptors](https://github.com/unjs/ofetch?tab=readme-ov-file#%EF%B8%8F-interceptors). Additional hooks will be added/supported as needed.
175+
176+
**Example** – Appending `/replication` to `Property` requests:
177+
178+
```js
179+
hooks: {
180+
onRequest: [
181+
({ request }) => {
182+
if (request.toLowerCase().includes('property')) {
183+
request += '/replication';
184+
}
185+
},
186+
],
187+
},
188+
```
189+
190+
### Error Handling
191+
192+
The client throws a `FeedError` with detailed RESO error info:
193+
194+
```js
195+
try {
196+
const myProperty = await feed.readById('Property', 123);
197+
console.log(myProperty.value);
198+
} catch (error) {
199+
if (error instanceof FeedError) {
200+
console.log(error.details);
201+
}
202+
}
203+
```
204+
205+
## Contributing
206+
207+
Bug reports, pull requests and feature discussions are welcome on [GitHub](https://github.com/ComfortablyCoding/reso.js)
208+
209+
## License
210+
211+
[MIT License](https://github.com/ComfortablyCoding/reso.js/blob/main/LICENSE)

eslint.config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import js from '@eslint/js';
2+
import json from '@eslint/json';
3+
import { defineConfig, globalIgnores } from 'eslint/config';
4+
import globals from 'globals';
5+
import tseslint from 'typescript-eslint';
6+
7+
export default defineConfig([
8+
{
9+
files: ['**/*.{js,mjs,cjs,ts}'],
10+
plugins: { js },
11+
extends: ['js/recommended'],
12+
},
13+
{
14+
files: ['**/*.{js,mjs,cjs,ts}'],
15+
languageOptions: { globals: globals.node },
16+
},
17+
tseslint.configs.recommended,
18+
{
19+
files: ['**/*.json'],
20+
plugins: { json },
21+
language: 'json/json',
22+
extends: ['json/recommended'],
23+
},
24+
globalIgnores(['dist']),
25+
]);

0 commit comments

Comments
 (0)