Updated documentation
All checks were successful
Build / Build NPM Project (push) Successful in 36s
Build / Tag Version (push) Successful in 7s
Build / Publish (push) Successful in 16s

This commit is contained in:
Zakary Timson 2024-01-07 19:11:26 -05:00
parent 7a9ab68402
commit 034134a08f
10 changed files with 4055 additions and 182 deletions

86
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Build
run-name: Build
on:
- push
- pull_request
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container:
image: node
volumes:
- '/mnt/swarm/gitea/runner/cache:/cache'
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Restore node_modules
uses: ztimson/actions/cache/restore@develop
with:
key: node_modules
- name: Install Dependencies
run: npm i
- name: Build Project
run: npm run build
- name: Test
run: npm run test:coverage
- name: Cache node_modules
uses: ztimson/actions/cache@develop
with:
key: node_modules
pattern: node_modules
- name: Cache Artifacts
uses: ztimson/actions/cache@develop
with:
pattern: dist
tag:
name: Tag Version
needs: build
if: ${{github.ref_name}} == 'release'
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}
publish:
name: Publish
needs: build
if: ${{github.ref_name}} == 'release'
runs-on: ubuntu-latest
container:
image: node
volumes:
- '/mnt/swarm/gitea/runner/cache:/cache'
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Restore Artifacts
uses: ztimson/actions/cache/restore@develop
- name: Upload to Registry
uses: ztimson/actions/npm/publish@develop
- name: Upload to NPM
uses: ztimson/actions/npm/publish@develop
with:
owner: ztimson
registry: https://registry.npmjs.org/
token: ${{secrets.NPM_TOKEN}}

View File

@ -1,105 +0,0 @@
image: node:16
npm:
stage: build
cache:
- key:
files:
- package-lock.json
paths:
- node_modules
policy: pull-push
- key: $CI_PIPELINE_ID
paths:
- dist
policy: push
script:
- npm install
- npm run build
artifacts:
paths:
- dist
expire_in: 1 week
rules:
- if: $CI_COMMIT_BRANCH
audit:
stage: test
cache:
- key:
files:
- package-lock.json
paths:
- node_modules
policy: pull
script:
- echo "vulnerabilities_high $(npm audit | grep -oE '[0-9]+ high' | grep -oE '[0-9]+' || echo 0)" > metrics.txt
- echo "vulnerabilities_medium $(npm audit | grep -oE '[0-9]+ moderate' | grep -oE '[0-9]+' || echo 0)" >> metrics.txt
- echo "vulnerabilities_low $(npm audit | grep -oE '[0-9]+ low' | grep -oE '[0-9]+' || echo 0)" >> metrics.txt
artifacts:
reports:
metrics: metrics.txt
rules:
- if: $CI_COMMIT_BRANCH
jest:
stage: test
cache:
- key:
files:
- package.json
paths:
- node_modules
policy: pull
script:
- npm run test:coverage
coverage: /All\sfiles.*?\s+(\d+.\d+)/
artifacts:
when: always
reports:
junit: junit.xml
rules:
- if: $CI_COMMIT_BRANCH
registry:
stage: deploy
cache:
- key:
files:
- package.json
paths:
- node_modules
policy: pull
- key: $CI_PIPELINE_ID
paths:
- dist
policy: pull
before_script:
- VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')
- if [ "$CI_COMMIT_BRANCH" != "$CI_DEFAULT_BRANCH" ] && [ "$VERSION" != *"-$CI_COMMIT_BRANCH" ]; then VERSION="$VERSION-$(echo "$CI_COMMIT_BRANCH" | sed -E "s/[_/]/-/g")"; npm version --no-git-tag-version $VERSION; fi
script:
- PACKAGES=$(curl -s -H "PRIVATE-TOKEN:$DEPLOY_TOKEN" https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages)
- ID=$(node -pe "JSON.parse(process.argv[1]).find(p => p['version'] == process.argv[2])?.id || ''" $PACKAGES $VERSION)
- if [ -n "$ID" ]; then curl -s -X DELETE -H "PRIVATE-TOKEN:$DEPLOY_TOKEN" https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages/$ID; fi
- printf "@transmute:registry=https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages/npm/\n//$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages/npm/:_authToken=$DEPLOY_TOKEN" > .npmrc
- npm publish
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: $CI_COMMIT_BRANCH
when: manual
allow_failure: true
tag:
stage: deploy
image:
name: alpine/git
entrypoint: [""]
cache: []
before_script:
- git remote set-url origin "https://ReleaseBot:$DEPLOY_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git"
script:
- VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')
- git tag -f $VERSION $CI_COMMIT_SHA
- git push -f origin $VERSION
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright (c) 2023 Zakary Timson
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.

148
README.md
View File

@ -1,10 +1,61 @@
# WebStorage Decorators <!-- Header -->
A Javascript library that adds property decorators to sync a class property with the local or session storage. Useful <div id="top" align="center">
for persisting themes or local settings over reloads or maintaining filters/search options in the current user session. <br />
<!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/60b47463c877db0d03d41f265a24df35882f246e95df786de38a19fbb617d310" alt="Logo" width="200" height="200">
<!-- Title -->
### webstorage-decorators
<!-- Description -->
TypeScript: Sync variables with localStorage
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators/tags&query=$[0].name)](https://git.zakscode.com/ztimson/webstorage-decorators/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators&query=open_pr_counter)](https://git.zakscode.com/ztimson/webstorage-decorators/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators&query=open_issues_count)](https://git.zakscode.com/ztimson/webstorage-decorators/issues)
<!-- Links -->
---
<div>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/releases" target="_blank">Release Notes</a>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div>
---
</div>
## Table of Contents
- [WebStorage-Decorators](#top)
- [About](#about)
- [Examples](#examples)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [Development](#development)
- [Documentation](#documentation)
- [Decorators](#decorators)
- [WebStorageOptions](#webstorageoptions)
- [License](#license)
## About
**Deprecated:** Please use the replacement library [var-persist](https://git.zakscode.com/ztimson/var-persist).
WebStorage-Decorators is a library that adds property decorators to sync a class property with the local or session storage. It is useful for persisting state through reloads without making any changes to existing code.
This library has some caveats that [var-persist](https://git.zakscode.com/ztimson/var-persist) rectifies:
- Only supports decorators
- Impure functions can make changes to data which do not get synced
- [Proto]types and functions are lost
**Disclaimer:** JavaScript's decorators are currently undergoing changes to the API overseen by [TC39](https://tc39.es) and currently have no support for property decorators. [Experimental decorators](https://www.typescriptlang.org/tsconfig#experimentalDecorators) must be enabled to work properly.
### Examples
## Quick Setup
1. Install with: `npm install --save webstorage-decorators`
2. Add the decorator to your property and use as normal!
```typescript ```typescript
import {LocalStorage, SessionStorage} from 'webstorage-decorators'; import {LocalStorage, SessionStorage} from 'webstorage-decorators';
@ -22,6 +73,54 @@ export class MyCustomClass {
} }
``` ```
### Built With
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup
<details>
<summary>
<h3 id="production" style="display: inline">
Production
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install persist: `npm i webstorage-decorators`
2. Enable decorators inside `tsconfig.json`:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
...
}
```
3. Import & use, see [examples above](#examples)
</details>
<details>
<summary>
<h3 id="development" style="display: inline">
Development
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install the dependencies: `npm i`
2. Build library & docs: `npm build`
3. Run unit tests: `npm test`
</details>
## Documentation ## Documentation
### Decorators ### Decorators
@ -38,39 +137,8 @@ export class MyCustomClass {
| default | Default value, same as decorator's first argument | | default | Default value, same as decorator's first argument |
| key | Key to reference value inside local/session storage (Defaults to the property name) | | key | Key to reference value inside local/session storage (Defaults to the property name) |
## Caveats ## License
### Custom Functions Copyright © 2023 Zakary Timson | Available under MIT Licensing
You can technically store anything inside local/session storage however everything is serialized using javascript's JSON,
so anything extra (prototypes, functions, etc) will be lost. However if you provide a default value, it will be copied &
the data injected, giving you a workaround to accessing static properties (Does not work with arrays).
```typescript See the [license](./LICENSE) for more information.
class Person {
constructor(public first: string, public last: string) { }
fullName() { return `${this.last}, ${this.first}`; }
}
LocalStorage.setItem('example', '{"first": "John", "last": "Smith"}');
@LocalStorage(null) example!: Person;
console.log(example.fullName()) // ERROR: fullName function doesn't exist
LocalStorage.setItem('example2', '{"first": "John", "last": "Smith"}');
@LocalStorage(new Person(null, null)) example2!: Person;
console.log(example2.fullName()) // Works because we have a default object to copy type from
```
### Impure Functions
Impure functions don't use the Object's setter preventing the storage from being updated. To prevent this use a pure
function or save it manually by reading the variable. (Reading triggers change detection & save if there are differences)
```typescript
@LocalStorage([1, 2]) example: number[];
example.push(3) // Impure & won't update storage
console.log(localStorage.getItem('example')) // Output: [1, 2];
example; // Trigger save
console.log(localStorage.getItem('example')) // Output: [1, 2, 3];
// OR
example = example.concat([3]); // Pure function requires you to use the setter triggering automatic saving
```

View File

@ -10,7 +10,7 @@ module.exports = {
".+\\.(ts)$": "ts-jest" ".+\\.(ts)$": "ts-jest"
}, },
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/utils/**/*.ts', 'src/**/*.ts',
'!src/**/utils/**/*.d.ts' '!src/**/*.d.ts'
], ],
}; };

3789
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,36 @@
{ {
"name": "webstorage-decorators", "name": "webstorage-decorators",
"version": "4.3.0", "version": "4.3.1",
"description": "Decorators to sync class properties to Local/Session storage", "description": "Decorators to sync class properties to Local/Session storage",
"repository": "https://github.com/ztimson/WebstorageDecorators", "repository": {
"author": "Zak Timson", "type": "git",
"license": "ISC", "url": "https://git.zakscode.com/ztimson/webstorage-decorators"
"main": "dist/index.js", },
"types": "dist/index.d.ts", "author": "Zak Timson",
"scripts": { "license": "MIT",
"build": "tsc", "main": "dist/index.js",
"test": "jest --verbose", "types": "dist/index.d.ts",
"test:coverage": "npx jest --verbose --coverage", "scripts": {
"watch": "npm run build && tsc --watch" "build": "tsc",
}, "test": "jest --verbose",
"keywords": [ "test:coverage": "npx jest --verbose --coverage",
"Decorators", "watch": "npm run build && tsc --watch"
"LocalStorage", },
"SessionStorage", "keywords": [
"WebStorage" "Decorators",
], "LocalStorage",
"dependencies": {}, "SessionStorage",
"devDependencies": { "WebStorage"
"@types/jest": "^29.2.3", ],
"jest": "^29.3.1", "dependencies": {},
"jest-junit": "^15.0.0", "devDependencies": {
"ts-jest": "^29.0.3", "@types/jest": "^29.5.11",
"typescript": "^4.9.3" "jest": "^29.7.0",
}, "jest-junit": "^16.0.0",
"files": [ "ts-jest": "^29.1.1",
"dist" "typescript": "^5.0.4"
] },
"files": [
"dist"
]
} }

View File

@ -41,7 +41,7 @@ export function LocalStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
* @param defaultValue Default value to return if property does no exist inside sessionStorage. * @param defaultValue Default value to return if property does no exist inside sessionStorage.
* @param opts Any additional options * @param opts Any additional options
*/ */
export function SessionStorage(defaultValue?, opts: WebStorageOptions = {}) { export function SessionStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
opts.default = defaultValue; opts.default = defaultValue;
return decoratorBuilder(sessionStorage, opts); return decoratorBuilder(sessionStorage, opts);
} }
@ -85,7 +85,7 @@ function decoratorBuilder(storage: Storage, opts: WebStorageOptions) {
let field = fromStorage(storage, opts); let field = fromStorage(storage, opts);
Object.defineProperty(target, key, { Object.defineProperty(target, key, {
get: function() { get: function() {
if(field != fromStorage(storage, {key: opts.key})) target[key] = field; if(field != fromStorage(storage, {key: opts.key})) (<any>target)[key] = field;
return field; return field;
}, },
set: function(value?) { set: function(value?) {

View File

@ -1,7 +1,27 @@
import {LocalStorage, SessionStorage} from "../src"; import {LocalStorage, SessionStorage} from "../src";
const CUSTOM_KEY = '_MY_KEY' // Mocks ===============================================================================================================
class StorageMock implements Storage {
private store: {[key: string]: string} = {}
get length() { return Object.keys(this.store).length; }
clear() { this.store = {}; }
getItem(key: string) { return this.store[key]; }
setItem(key: string, value: string) { this.store[key] = value; }
removeItem(key: string) { delete this.store[key]; }
key(index: number) { return Object.keys(this.store)[index]; }
}
(<any>global).localStorage = new StorageMock();
(<any>global).sessionStorage = new StorageMock()
var localStorage: StorageMock;
localStorage = (<any>global).localStorage;
var sessionStorage: StorageMock;
sessionStorage = (<any>global).sessionStorage;
// Test Data ===========================================================================================================
const CUSTOM_KEY = '_MY_KEY'
class TestType { class TestType {
constructor(public first: string, public last: string) { } constructor(public first: string, public last: string) { }
fullName() { return `${this.last}, ${this.first}`; } fullName() { return `${this.last}, ${this.first}`; }
@ -18,6 +38,7 @@ class TestStorage {
@SessionStorage(new TestType('John', 'Smith')) objectSessionStorage!: TestType; @SessionStorage(new TestType('John', 'Smith')) objectSessionStorage!: TestType;
} }
// Tests ===============================================================================================================
describe('WebStorage Decorators', () => { describe('WebStorage Decorators', () => {
let testComponent: TestStorage; let testComponent: TestStorage;
beforeEach(() => { beforeEach(() => {

View File

@ -2,7 +2,11 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"lib": ["ESNext"], "experimentalDecorators": true,
"lib": [
"ESNext",
"DOM"
],
"module": "commonjs", "module": "commonjs",
"outDir": "./dist", "outDir": "./dist",
"strict": true, "strict": true,