
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@thin-storage/core
Advanced tools
$fancy keywordsThis tool allows to CRUD a local in-memory store and provides a minimal middleware stack, which in turn allows to implement your own sync system to whatever will actually store the data.
It brings no sync or replication system but a simple, yet flexible API to help you with it. It also does not distinct between store and collections - every instance is a collection where you can either reuse or mix the middleware stack for each of them individually.
Reactivity is not "baked in" but possible to achieve, which is also described in the integrations section.
This approach keeps the package small and concise.
First, install from npm via
$ npm install thin-storage
Let's create a minimal storage that has no middleware and runs solely in-memory:
import { ThinStorage } from 'thin-storage'
const storage = new ThinStorage()
Now, let's add and query some documents. It's super simple:
await storage.insert({ foo: 'bar' })
await storage.insert([{ bar: 'baz' }])
storage.find({ yolo: 1 }) // []
storage.find({ foo: 'bar' }) // [{ id: '0000000000000001', foo: 'bar' }]
storage.find(doc => 'bar' in doc) // [{ id: '0000000000000002', bar: 'baz' }]
storage.find() // [{...}, {...}]
As you can see the storage will generate values for the default primary key id,
since there is currently no middleware handler implementing insert.
You can read more on this in the primary key section.
Now let's update some documents:
const query = { foo: 'bar' }
const modifier = { foo: 'moo', yolo: 1 }
await storage.update(query, modifier)
storage.find({ foo: 'bar' })
// [{ id: '0000000000000001', foo: 'moo', yolo: 1 }]
The update query follows the exact same rules as queries for the find method.
Here, we select all documents where foo exactly equals 'bar'.
Read the queries section for all possibilities to build queries.
The modifier however in this example changes in all found documents the foo property to 'moo'
and yolo to 1. If the properties are not present in the queried documents then they will be added
with the given values. More modifications are described in the modifiers section.
Both are described in the rules section.
Finally, let's remove some documents:
const query = doc => ['foo', 'bar'].some(val => val in doc)
await storage.remove(query)
storage.find({})
// []
The query is now a function. Basically, it checks, whether a doc contains foo or bar as property.
This resulted in all documents being selected for removal, which is why we ended up with an empty storage.
That's pretty much it so far for the introduction. Wait, there is also fetching documents!
Let me explain, why and how its different from find.
A simple approach to reuse the middleware stack for each instance is to use a factory function:
const handler = [{
async insert () { ... },
async update () { ... },
async remove () { ... }
}, {
async fetch () { ... },
async insert () { ... },
async update () { ... },
async remove () { ... }
}]
export const createStorage = ({ name, primary = 'id' }) => {
return new Storage({ name, primary, handler })
}
There are a few simple rules to know, in order to construct valid queries and modifiers.
In contrast to other storage tools we don't use $fancy keys but simple conventions.
If they don't cover your use-case - worry not - you can still provide a callback to construct entirely custom queries and modifiers! 💪
We assume there is always some primary key for a collection of documents, the default value is id but
you can change it to anything you need to (for example in MongoDB it's typically _id).
There are three ways to generate primary keys:
idGen optionQueries are used to define the subset of documents that is used for find, update or remove operations.
The following constructs are possible:
a single string ⇒
'217db87c'a list of strings ⇒
['7970d267', 'e818085e', '47d5df93']an object with key-value pairs ⇒
{foo: 'bar'} ⇒ find all docs with property foo being 'bar'{foo: ['bar', 'moo']} ⇒ find all docs with property foo being 'bar' or 'moo'{foo: val => /moo/i.test(val)} ⇒ find all docs that pass the function match test,
in this example represented by a RegEx testa callback-style function ⇒
doc => 'foo' in doc ⇒ returns all docs that have the foo propertyModifiers define how documents should be updated. If a document matches the query then the modification will be applied to it. The following constructs are possible:
{foo: 'moo'} ⇒ changes the value of the foo property to 'moo' in all queried documents{foo: 'null'}) the property will be deleted from the documentdoc => doc.moo += 1 increments moo by 1, assuming it exists as a number in the given documentsdoc => { doc.moo = doc.moo ? 0 : doc.moo + 1; return doc }The local documents in the storage are contained within a Set.
To observe changes using a ref, simply pass the ref value as set argument to the options
when constructing the storage:
<script setup>
import { ref } from 'vue'
import { ThinStorage } from '@thin-storage/core'
const users = ref(new Set())
const UsersCollection = new ThinStorage({
name: 'users',
set: users.value
})
// this operation will be reflected on the ref
UsersCollection
.insert({ username: 'johnnyCash01234' })
.catch(console.error)
</script>
In your template you must use the .ref to access properties reactively:
<ul>
<li v-for="user in users" :key="user.ref.id">
<span>{{ user.ref.username }}</span>
</li>
</ul>
The ref has nothing to do with the ref from Vue but is a way to
allow document updates within a Set without neither removing them nor
requiring overly complex diff algorithms.
React's useState requires data to be immutable which
is why we added a simple EventEmitter-like functionality that dispatches changes, so you can listen to and
update state as desired:
export const useStorage = (storage, query = {}) => {
const [docs, setDocs] = useState(() => storage.find(query))
useEffect(() => {
const off = storage.on('change', () => setDocs(storage.find(query)))
return () => off()
}, [])
return docs
}
The following events are dispatched:
| Event | When | Args |
|---|---|---|
change | Any change to the documents set | undefined |
insert | new documents are insert | Array of the inserted docs |
update | documents are updated | Array of the updated docs |
remove | documents were removed | Array of the removed docs |
Thanks a lot for your intent to contributing to this project and free software in general. The following sections will help you to get started as fast and easy as possible to make your contribution a success!
We use the following stack to develop and publish this package:
All tools are defined as dev-dependencies!
Note: We provide an extensive contribution guideline and a code of conduct to help you in making your contribution a success!
First, or fork the repository and clone it to your local machine:
$ git clone git@github.com:jankapunkt/thin-storage.git
From here, simply create your Js files in the ./lib folder and add the tests in the test folder.
We provide a default set of tools via npm scripts. Run a script via
$ npm run <command>
where <command> is one of the following available commands:
| command | description | output |
|---|---|---|
lint | runs the linter in read-mode | |
lint:fix | runs the linter; fixes minor issues automatically | |
test | runs the tests once | |
test:watch | runs the tests; re-runs them on code changes | |
test:coverage | runs the tests once and creates a coverage report | coverage |
docs | creates API documentation | docs |
build | builds the bundles for several target platforms | dist |
build:full | runs build and docs | see above |
Please read our security policy to get to know which versions are covered.
MIT, see license file
FAQs
Lightweight storage interface with middleware stack
We found that @thin-storage/core demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.