-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: support minified class names (#19)
* chore: add this.setMinifiedName and remove Type suffix * fix: allow developers to indicate the class name for minification * docs: separate and expand existing docs * ci: upload code coverage to deepsource
- Loading branch information
1 parent
2d4c028
commit 85d884a
Showing
32 changed files
with
2,226 additions
and
384 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,243 +1,29 @@ | ||
# @acodeninja/persist | ||
|
||
A JSON based data modelling and persistence library with alternate storage mechanisms. | ||
A JSON based data modelling and persistence library with alternate storage mechanisms, designed with static site generation in mind. | ||
|
||
## Models | ||
![NPM Version](https://img.shields.io/npm/v/%40acodeninja%2Fpersist) | ||
![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/%40acodeninja%2Fpersist) | ||
![GitHub top language](https://img.shields.io/github/languages/top/acodeninja/persist) | ||
![NPM Downloads](https://img.shields.io/npm/dw/%40acodeninja%2Fpersist) | ||
|
||
The `Model` and `Type` classes allow creating representations of data objects | ||
[![DeepSource](https://app.deepsource.com/gh/acodeninja/persist.svg/?label=active+issues&show_trend=true&token=Vd8_PJuRwwoq4_uBJ0_ymc06)](https://app.deepsource.com/gh/acodeninja/persist/) | ||
[![DeepSource](https://app.deepsource.com/gh/acodeninja/persist.svg/?label=code+coverage&show_trend=true&token=Vd8_PJuRwwoq4_uBJ0_ymc06)](https://app.deepsource.com/gh/acodeninja/persist/) | ||
|
||
### Defining Models | ||
## Features | ||
|
||
##### A model using all available basic types | ||
- Data modelling with relationships | ||
- Data validation | ||
- Data querying | ||
- Fuzzy search | ||
- Storage with: S3, HTTP and Filesystem | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
## Find out more | ||
|
||
export class SimpleModel extends Persist.Type.Model { | ||
static boolean = Persist.Type.Boolean; | ||
static string = Persist.Type.String; | ||
static number = Persist.Type.Number; | ||
static date = Persist.Type.Date; | ||
} | ||
``` | ||
|
||
##### A simple model using required types | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class SimpleModel extends Persist.Type.Model { | ||
static requiredBoolean = Persist.Type.Boolean.required; | ||
static requiredString = Persist.Type.String.required; | ||
static requiredNumber = Persist.Type.Number.required; | ||
static requiredDate = Persist.Type.Date.required; | ||
} | ||
``` | ||
|
||
##### A simple model using arrays of basic types | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class SimpleModel extends Persist.Type.Model { | ||
static arrayOfBooleans = Persist.Type.Array.of(Type.Boolean); | ||
static arrayOfStrings = Persist.Type.Array.of(Type.String); | ||
static arrayOfNumbers = Persist.Type.Array.of(Type.Number); | ||
static arrayOfDates = Persist.Type.Array.of(Type.Date); | ||
static requiredArrayOfBooleans = Persist.Type.Array.of(Type.Boolean).required; | ||
static requiredArrayOfStrings = Persist.Type.Array.of(Type.String).required; | ||
static requiredArrayOfNumbers = Persist.Type.Array.of(Type.Number).required; | ||
static requiredArrayOfDates = Persist.Type.Array.of(Type.Date).required; | ||
} | ||
``` | ||
|
||
<details> | ||
<summary>Complex relationships are also supported</summary> | ||
|
||
#### One-to-One Relationships | ||
|
||
##### A one-to-one relationship | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class ModelB extends Persist.Type.Model { | ||
} | ||
|
||
export class ModelA extends Persist.Type.Model { | ||
static linked = ModelB; | ||
} | ||
``` | ||
|
||
##### A circular one-to-one relationship | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class ModelA extends Persist.Type.Model { | ||
static linked = () => ModelB; | ||
} | ||
|
||
export class ModelB extends Persist.Type.Model { | ||
static linked = ModelA; | ||
} | ||
``` | ||
|
||
#### One-to-Many Relationships | ||
|
||
##### A one-to-many relationship | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class ModelB extends Persist.Type.Model { | ||
} | ||
|
||
export class ModelA extends Persist.Type.Model { | ||
static linked = Persist.Type.Array.of(ModelB); | ||
} | ||
``` | ||
|
||
##### A circular one-to-many relationship | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class ModelA extends Persist.Type.Model { | ||
static linked = () => Type.Array.of(ModelB); | ||
} | ||
|
||
export class ModelB extends Persist.Type.Model { | ||
static linked = ModelA; | ||
} | ||
``` | ||
|
||
#### Many-to-Many Relationships | ||
|
||
##### A many-to-many relationship | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class ModelA extends Persist.Type.Model { | ||
static linked = Persist.Type.Array.of(ModelB); | ||
} | ||
|
||
export class ModelB extends Persist.Type.Model { | ||
static linked = Persist.Type.Array.of(ModelA); | ||
} | ||
``` | ||
</details> | ||
|
||
## Find and Search | ||
|
||
Models may expose a `searchProperties()` and `indexProperties()` static method to indicate which | ||
fields should be indexed for storage engine `find()` and `search()` methods. | ||
|
||
Use `find()` for a low usage exact string match on any indexed attribute of a model. | ||
|
||
Use `search()` for a medium usage fuzzy string match on any search indexed attribute of a model. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import FileEngine from "@acodeninja/persist/engine/file"; | ||
|
||
export class Tag extends Persist.Type.Model { | ||
static tag = Persist.Type.String.required; | ||
static description = Persist.Type.String; | ||
static searchProperties = () => ['tag', 'description']; | ||
static indexProperties = () => ['tag']; | ||
} | ||
|
||
const tag = new Tag({tag: 'documentation', description: 'How to use the persist library'}); | ||
|
||
await FileEngine.find(Tag, {tag: 'documentation'}); | ||
// [Tag {tag: 'documentation', description: 'How to use the persist library'}] | ||
|
||
await FileEngine.search(Tag, 'how to'); | ||
// [Tag {tag: 'documentation', description: 'How to use the persist library'}] | ||
``` | ||
|
||
## Storage | ||
|
||
### Filesystem Storage Engine | ||
|
||
To store models using the local file system, use the `File` storage engine. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import FileEngine from "@acodeninja/persist/engine/file"; | ||
|
||
Persist.addEngine('local', FileEngine, { | ||
path: '/app/storage', | ||
}); | ||
|
||
export class Tag extends Persist.Type.Model { | ||
static tag = Persist.Type.String.required; | ||
} | ||
|
||
await Persist.getEngine('local', FileEngine).put(new Tag({tag: 'documentation'})); | ||
``` | ||
|
||
### HTTP Storage Engine | ||
|
||
To store models using an S3 Bucket, use the `S3` storage engine. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import HTTPEngine from "@acodeninja/persist/engine/http"; | ||
|
||
Persist.addEngine('remote', HTTPEngine, { | ||
host: 'https://api.example.com', | ||
}); | ||
|
||
export class Tag extends Persist.Type.Model { | ||
static tag = Persist.Type.String.required; | ||
} | ||
|
||
await Persist.getEngine('remote', HTTPEngine).put(new Tag({tag: 'documentation'})); | ||
``` | ||
|
||
### S3 Storage Engine | ||
|
||
To store models using an S3 Bucket, use the `S3` storage engine. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import S3Engine from "@acodeninja/persist/engine/s3"; | ||
|
||
Persist.addEngine('remote', S3Engine, { | ||
bucket: 'test-bucket', | ||
client: new S3Client(), | ||
}); | ||
|
||
export class Tag extends Persist.Type.Model { | ||
static tag = Persist.Type.String.required; | ||
} | ||
|
||
await Persist.getEngine('remote', S3Engine).put(new Tag({tag: 'documentation'})); | ||
``` | ||
|
||
## Transactions | ||
|
||
Create transactions to automatically roll back on failure to update. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import S3Engine from "@acodeninja/persist/engine/s3"; | ||
|
||
Persist.addEngine('remote', S3Engine, { | ||
bucket: 'test-bucket', | ||
client: new S3Client(), | ||
transactions: true, | ||
}); | ||
|
||
export class Tag extends Persist.Type.Model { | ||
static tag = Persist.Type.String.required; | ||
} | ||
|
||
const transaction = Persist.getEngine('remote', S3Engine).start(); | ||
|
||
await transaction.put(new Tag({tag: 'documentation'})); | ||
await transaction.commit(); | ||
``` | ||
- [Model Property Types](./docs/model-property-types.md) | ||
- [Models as Properties](./docs/models-as-properties.md) | ||
- [Structured Queries](./docs/structured-queries.md) | ||
- [Search Queries](./docs/search-queries.md) | ||
- [Storage Engines](./docs/storage-engines.md) | ||
- [Transactions](./docs/transactions.md) | ||
- [Quirks](./docs/code-quirks.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Code Quirks | ||
|
||
When using Persist in a minified or bundled codebase, it's important to be aware of two key quirks: handling class names during minification and managing reference errors when working with model relationships. | ||
|
||
## Class Names and Minification | ||
|
||
When you bundle or minify JavaScript code for production, class names are often altered, which can cause issues. Specifically, models may lose their original class names, which we rely on for storing data in the correct namespace. | ||
|
||
To avoid this problem, you have two options: | ||
|
||
1. Disable class name mangling in your minifier. | ||
2. Use `this.setMinifiedName(name)` to manually specify the model's name. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class Person extends Persist.Type.Model { | ||
static { | ||
this.setMinifiedName('Person'); | ||
this.name = Persist.Type.String.required; | ||
} | ||
} | ||
``` | ||
|
||
If you don't set the minified name, the wrong namespace may be used when saving models, leading to unexpected behavior. | ||
|
||
## Reference Errors | ||
|
||
When defining relationships between models, especially circular relationships (e.g., `Person` references `Address`, and `Address` references `Person`), the order of declarations matters. If the models are referenced before they are initialized, you'll encounter `ReferenceError` messages, like: | ||
|
||
```console | ||
ReferenceError: Cannot access 'Address' before initialization | ||
``` | ||
|
||
To avoid these errors, always define model relationships using arrow functions. For example: | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
|
||
export class Person extends Persist.Type.Model { | ||
static { | ||
this.address = () => Address; | ||
} | ||
} | ||
|
||
export class Address extends Persist.Type.Model { | ||
static { | ||
this.person = () => Person; | ||
this.address = Persist.Type.String.required; | ||
this.postcode = Persist.Type.String.required; | ||
} | ||
} | ||
``` | ||
|
||
By doing this, you ensure that model references are evaluated lazily, after all models have been initialized, preventing `ReferenceError` issues. | ||
|
||
## Using `HTTP` Engine in Browser | ||
|
||
When implementing thee `HTTP` engine for code that runs in the web browser, you must pass `fetch` into the engine configuration and bind it to the `window` object. | ||
|
||
```javascript | ||
import Persist from "@acodeninja/persist"; | ||
import HTTPEngine from "@acodeninja/persist/engine/http"; | ||
|
||
Persist.addEngine('remote', HTTPEngine, { | ||
host: 'https://api.example.com', | ||
fetch: fetch.bind(window), | ||
}); | ||
``` | ||
|
||
This will ensure that `fetch` can access the window context which is required for it to function. |
Oops, something went wrong.