Skip to content

Commit

Permalink
Merge pull request #3 from privatenumber/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
privatenumber authored Aug 30, 2020
2 parents 951a5a4 + 586a50f commit c25a7bb
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 30 deletions.
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
# reactive-json <a href="https://npm.im/reactive-json"><img src="https://badgen.net/npm/v/reactive-json"></a> <a href="https://npm.im/reactive-json"><img src="https://badgen.net/npm/dm/reactive-json"></a> <a href="https://packagephobia.now.sh/result?p=reactive-json"><img src="https://packagephobia.now.sh/badge?p=reactive-json"></a> <a href="https://bundlephobia.com/result?p=reactive-json"><img src="https://badgen.net/bundlephobia/minzip/reactive-json"></a>
# reactive-json-file <a href="https://npm.im/reactive-json-file"><img src="https://badgen.net/npm/v/reactive-json-file"></a> <a href="https://npm.im/reactive-json-file"><img src="https://badgen.net/npm/dm/reactive-json-file"></a> <a href="https://packagephobia.now.sh/result?p=reactive-json-file"><img src="https://packagephobia.now.sh/badge?p=reactive-json-file"></a> <a href="https://bundlephobia.com/result?p=reactive-json-file"><img src="https://badgen.net/bundlephobia/minzip/reactive-json-file"></a>

Immediate save your JS objects to a JSON file as you mutate them
Save your JS objects to a JSON file as you mutate them

## :raising_hand: Why?
- 🐥 Tiny!
```js
import reactiveJsonFile from 'reactive-json-file'

// Create a new JSON
const object = reactiveJsonFile('./data.json')

// Mutating the object automatically saves to file
object.name = 'John Doe'

```

## :rocket: Install
```sh
npm i reactive-json
npm i reactive-json-file
```


## ⚙️ Options
- `throttle` `<Number>` - Milliseconds to throttle saves by. Saves are already batched at the end of every event-loop, but this adds time-based throttling.
- `fs` `<FileSystemInterface>` ([fs](https://nodejs.org/api/fs.html)) - Pass in a custom file-system. Defaults to native Node.js fs
- `serialize`/`deserialize` `<Function>` - Functions to serialize/deserialize the object with. eg. to save output to YAML

```js
import reactiveJsonFile from 'reactive-json-file'
import yaml from 'js-yaml'

const object = reactiveJsonFile('./file.yaml', {
serialize: string => yaml.dump(string),
deserialize: object_ => yaml.load(object_)
})

object.message = 'YAML!'
```

## 🙋‍♀️ FAQ

### How does it work?
Arbitrary new changes are detected by using Vue 3's reactive API, which uses [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) behind the scenes.
10 changes: 8 additions & 2 deletions lib/reactive-json.js → lib/reactive-json-file.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {reactive, watch} = require('vue');
const _throttle = require('lodash.throttle');

const readFile = (fs, filepath) => {
try {
Expand All @@ -15,12 +16,17 @@ function reactiveJson(filepath, {
fs = require('fs'),
serialize = jsonSerialize,
deserialize = jsonDeserialize,
throttle = undefined,
} = {}) {
const object = reactive(deserialize(readFile(fs, filepath)));
let writeFn = () => fs.writeFileSync(filepath, serialize(object));
if (typeof throttle === 'number') {
writeFn = _throttle(writeFn, throttle);
}

const object = reactive(deserialize(readFile(fs, filepath)));
watch(
() => object,
() => fs.writeFileSync(filepath, serialize(object)),
writeFn,
{deep: true},
);

Expand Down
9 changes: 7 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 18 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
{
"name": "reactive-json",
"version": "1.0.0",
"description": "",
"main": "lib/reactive-json.js",
"name": "reactive-json-file",
"version": "0.0.0-semantic-release",
"description": "Reactively save objects to file",
"keywords": [
"reactive",
"vue",
"json",
"save",
"file"
],
"repository": "privatenumber/reactive-json-file",
"author": "Hiroki Osame <[email protected]>",
"license": "MIT",
"files": [
"lib"
],
"main": "lib/reactive-json-file.js",
"scripts": {
"test": "jest",
"lint": "xo"
Expand All @@ -15,17 +28,8 @@
"lint-staged": {
"*.js": "xo"
},
"repository": {
"type": "git",
"url": "git+https://github.com/privatenumber/reactive-json.git"
},
"author": "Hiroki Osame <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/privatenumber/reactive-json/issues"
},
"homepage": "https://github.com/privatenumber/reactive-json#readme",
"dependencies": {
"lodash.throttle": "^4.1.1",
"vue": "^3.0.0-rc.9"
},
"devDependencies": {
Expand Down
42 changes: 35 additions & 7 deletions test/reactive-json.spec.js → test/reactive-json-file.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const reactiveJson = require('..');
const reactiveJsonFile = require('..');
const {Volume} = require('memfs');
const yaml = require('js-yaml');

const sleep = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});

const nextTick = () => new Promise(resolve => {
process.nextTick(resolve);
});
Expand All @@ -16,7 +20,7 @@ beforeEach(() => {

describe('write', () => {
test('basic use-case', async () => {
const object = reactiveJson(filepath, {fs});
const object = reactiveJsonFile(filepath, {fs});

object.randomProp = 'hello world';

Expand All @@ -26,7 +30,7 @@ describe('write', () => {
});

test('default object', async () => {
const object = Object.assign(reactiveJson(filepath, {fs}), {
const object = Object.assign(reactiveJsonFile(filepath, {fs}), {
name: 'default name',
age: 21,
});
Expand All @@ -43,7 +47,7 @@ describe('write', () => {
});

test('serialize/deserialize', async () => {
const object = reactiveJson(filepath, {
const object = reactiveJsonFile(filepath, {
fs,
serialize: string => yaml.dump(string),
deserialize: object_ => yaml.load(object_),
Expand All @@ -64,7 +68,7 @@ describe('read', () => {
gender: 'male',
}));

const object = reactiveJson(filepath, {fs});
const object = reactiveJsonFile(filepath, {fs});

expect(object).toMatchObject({
name: 'john doe',
Expand All @@ -78,7 +82,7 @@ describe('read', () => {
gender: 'male',
}));

const object = reactiveJson(filepath, {
const object = reactiveJsonFile(filepath, {
fs,
serialize: string => yaml.dump(string),
deserialize: object_ => yaml.load(object_),
Expand All @@ -93,7 +97,7 @@ describe('read', () => {

test('only call once', async () => {
const serialize = jest.fn(JSON.stringify);
const object = reactiveJson(filepath, {
const object = reactiveJsonFile(filepath, {
fs,
serialize,
});
Expand All @@ -107,3 +111,27 @@ test('only call once', async () => {

expect(serialize).toHaveBeenCalledTimes(1);
});

test('throttle', async () => {
const serialize = jest.fn(JSON.stringify);
const object = reactiveJsonFile(filepath, {
fs,
serialize,
throttle: 1000,
});

async function change(i) {
if (i === 0) {
return;
}

object.a = Math.random();
await sleep(100);
return change(--i);
}

await change(5);
await sleep(500);

expect(serialize).toHaveBeenCalledTimes(2);
});

0 comments on commit c25a7bb

Please sign in to comment.