Skip to content
This repository was archived by the owner on Sep 18, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.d.ts
dist
node_modules
.eslintrc.js
122 changes: 112 additions & 10 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,116 @@
/* global module */
module.exports = {
env: {
browser: true,
es2021: true
},
extends: ['airbnb-base'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module'
plugins: ['import', 'unused-imports'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-comments/recommended',
'plugin:promise/recommended',
'prettier',
'prettier/@typescript-eslint'
],
root: true,
env: {
node: true,
jest: true
},
plugins: ['@typescript-eslint'],
rules: {}
rules: {
// -- Extra rules --
// Reason: Missing curly braces (for example in if statements) are hard to read and may lead to bugs.
curly: 'warn',
// Reason: Logs should be only be done with fastify.
// Some logs are allowed for cli scripts.
'no-console': [
'error',
{
allow: ['info', 'warn', 'error', 'time', 'timeEnd']
}
],
// Reason: "any" disables the type checker and shouldn't be used.
'@typescript-eslint/no-explicit-any': 'error',
// Reason: All dependencies that are used in the App should be defined in package.json#dependencies.
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.spec.ts'],
optionalDependencies: false,
peerDependencies: false
}
],
// Reason: Automatic sorting of imports to avoid unnecessary merge conflicts.
'import/order': [
'warn',
{
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true
}
}
],
// Reason: This leads to bad performance and most of the time Promise.all
// should be used instead.
'no-await-in-loop': 'error',
// Reason: Improves readability.
'no-else-return': ['error', { allowElseIf: false }],
// Reason: Detects unnecessary code.
'no-extra-bind': 'error',
// Reason: Improves readability. An exception is the !! pattern with booleans
// as it is very common and well known.
'no-implicit-coercion': ['warn', { boolean: false }],
// Reason: Improves readability.
'no-lonely-if': 'warn',
// Reason: Detects unnecessary code.
'no-self-compare': 'error',
// Reason: Detects possible bugs.
'no-template-curly-in-string': 'error',
// Reason: Improves readability and performance.
'no-useless-call': 'error',
// Reason: Improves readability.
'no-useless-computed-key': 'warn',
// Reason: Improves readability.
'no-useless-concat': 'error',
// Reason: Detects unnecessary code.
'no-useless-return': 'error',
// Reason: Improves readability.
'object-shorthand': 'warn',
// Reason: Improves readability.
'prefer-const': ['warn', { ignoreReadBeforeAssign: true }],
// Reason: Improves readability.
'prefer-destructuring': 'warn',
// Reason: Improves readability and performance.
'prefer-numeric-literals': 'error',
// Reason: Improves readability.
'prefer-regex-literals': 'error',
// Reason: Improves readability.
'prefer-template': 'error',
// Reason: Thrown literal don't have stack traces.
'no-throw-literal': 'error',
// Reason: Detects unnecessary code.
'no-return-await': 'error',
// Reason: Unused imports increase the application size and decrease the performance
'unused-imports/no-unused-imports-ts': 'warn',
// Reason: Unused variables increase the application size and are confusing
'@typescript-eslint/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
// Reason: Detects unnecessary code.
'@typescript-eslint/no-useless-constructor': 'error',
// Reason: Improves readability.
'@typescript-eslint/prefer-as-const': 'error',

// -- Disabled rules --
// Reason: We have cli scripts that need to exit the app
'no-process-exit': 'off',
// Reason: Not necessary if the type can be inferred. Produces a lot of boilerplate.
'@typescript-eslint/explicit-module-boundary-types': 'off'
}
};
14 changes: 0 additions & 14 deletions .releaserc.json

This file was deleted.

61 changes: 26 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,46 @@

Offline-first IndexedDb wrapper written in TypeScript, which is able to sync with backend services by passing optional service implementation.

## Why?
## What reason exists for CrudoDb?

CrudoDb offers some advantages over other solutions as it offers an offline-first and offline-only solution.
CrudoDb allows you to write offline-first webapps without any backend implementation.

An optional API connection and can be synchronized e.g. after an already completed offline implementation.
In agile Projects, you can implement a PoC without any depend to the backend team.

## crudodb
A small and good tested implementation against fakeindexeddb and (at the moment) local developer tested solution.

Store list of `Database<T>` instances and manage them.
Indexeddb internal dbVersion will only incremented if necessary.

`schemaKey` is an identifier to the instance of `Database<T>` you want to access.

For example:
## Quick greenfield example

```typescript
const instance = await CrudoDb.setup();

// key will generated by CrudoDb
const key = await instance.registerSchema({
schema: mySchema
});
const item: T = { key: 'value', id: '1' };

// will call create of the Database<T> instance
instance.create(key, item);
```

## Database

* Manages all CRUD operations against store and optional API.
* Store item in indexeddb and on success try to call the api#create.
* if no API is defined, the item only lives in the IndexedDb.
* if an API is defined, the created item will be sent to the API.
* on successful creation, the local item will update without creation flag
const instance = await CrudoDb.setup();

### Flags
const schema: StoreSchema = {dbVersion:1,dbName:'test', indices: [{name:'a'},{name:'b'}]};

There are 3 flags to mark an item in indexeddb for processing.
const dao = await instance.applySchema({schema});

+ C
+ U
+ D
const entity = await dao.create({a: '42', b:'666'});
```

### Sync with API
## Recommendations

by default, no api is required to work with crudodb.
read in [documentation](docs) for angular and react recommendations.


## The Latest Test Coverage (local)

## Recommendations
from 2021-05-06

see [documentation](docs) for angular and react recommendations.
-----------------|---------|----------|---------|---------|----------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|----------------------------
All files | 93.29 | 87.5 | 91.45 | 94.62 |
crudodb.ts | 94.62 | 73.33 | 100 | 94.44 | 94-99,150,213
database.ts | 93.49 | 95.06 | 88.52 | 95.48 | 54,174,337-338,342-346,358
index.ts | 100 | 100 | 50 | 100 |
store-api.ts | 100 | 100 | 100 | 100 |
store-schema.ts | 100 | 100 | 100 | 100 |
utils.ts | 89.39 | 81.82 | 90.91 | 91.53 | 29,54-58,71
-----------------|---------|----------|---------|---------|----------------------------
34 changes: 25 additions & 9 deletions docs/Types.puml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Key <-- IDBKeyRange
interface CrudApi<T> {
create(obj: T): Promise<T|undefined>
update(obj: T): Promise<T|undefined>
delete(obj: T): Promise<boolean>
delete(obj: T): Promise<void>
get(id: Key): Promise<T|undefined>
gets(): Promise<T[]>
}
Expand All @@ -38,6 +38,12 @@ interface CheckApiOnline {
isOnline(): Promise<boolean>
}

interface RegisterSchemaArgs<T> {
StoreSchema schema
CrudApi<T>? api
string? schemaKey
}

class Database<T> {
string key
CrudApi<T>? api
Expand All @@ -46,17 +52,27 @@ class Database<T> {

class CrudoDb {
Record<string, Database> databases
Record<string, StoreApi> storeApis

addDatabase(db: Database<T>): void
gets<T>(schemaKey: string): Promise<T[]>
get<T>(id: Key, schemaKey: string): Promise<T|undefined>
create<T>(obj: T, schemaKey: string): Promise<T|undefined>
update<T>(obj: T, schemaKey: string): Promise<T|undefined>
delete<T>(obj: T, schemaKey: string): Promise<boolean>
get<T>(schemaKey: string, id: Key): Promise<T|undefined>
create<T>(schemaKey: string, obj: T): Promise<T|undefined>
update<T>(schemaKey: string, obj: T): Promise<T|undefined>
delete<T>(schemaKey: string, obj: T): Promise<void>

applySchema<T>(args: RegisterSchemaArgs<T>): Promise<StoreApi<T>>
registerSchema<T>(args: RegisterSchemaArgs<T>): Promise<string>

goingOnline(): void
sync(): void
syncDatabase<T>(database: Database<T>): void
sync(schemaKeys: string[]): Promise<void>
}

class StoreApi<T> {
CrudoDb db
string schemaKey

sync(): Promise<void>
}

CrudApi <-- StoreApi

@enduml
27 changes: 9 additions & 18 deletions docs/angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@

recommendation for angular is to use shared root service, which setup the instance.

all consuming services need a schemaKey. To work smooth with it, cache it in a property and optional combine it with the instance from root service.

In all following usages, you have to pipe `handle$` to the matching method
all consuming services need a StoreApi instance.

```typescript
import {Injectable} from '@angular/core';
import {combineLatest, Observable} from "rxjs";
import {map, shareReplay, switchMap, tap} from "rxjs/operators";
import {fromPromise} from "rxjs/internal-compatibility";
import {CrudoDb} from "crudodb";
import {StoreSchema} from "./store-schema";
import {combineLatest, Observable, from} from 'rxjs';
import {map, shareReplay, switchMap, tap} from 'rxjs/operators';
import {CrudoDb, StoreSchema} from 'crudodb';

@Injectable({providedIn: 'root'})
export class StoreAccessService {

public instance$ = fromPromise(CrudoDb.setup())
public instance$ = from(CrudoDb.setup())
.pipe(shareReplay(1));

}
Expand All @@ -40,23 +36,18 @@ const schema: StoreSchema = {
@Injectable({providedIn: 'root'})
export class DaoService {

private key$ = this.storeAccess.instance$.pipe(
switchMap((instance) => instance.registerSchema({schema})),
private db$ = this.storeAccess.instance$.pipe(
switchMap((instance) => instance.applySchema({schema})),
shareReplay(1)
);

private handle$ = combineLatest([
this.key$,
this.storeAccess.instance$
]).pipe(shareReplay(1));

constructor(
private storeAccess: StoreAccessService
) {}

public create(item: Dao): Observable<Dao> {
return this.handle$.pipe(
switchMap(([key, instance]) => instance.create(key, item))
return this.db$.pipe(
switchMap((db) => db.create(item))
);
}
}
Expand Down
11 changes: 10 additions & 1 deletion docs/react.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
React usage (hooks api)
# React usage (hooks api)

> only textual recommendation

1. CrudoDb instance should store in a root provider
2. use a generic or specific hook for your schema
1. provide context for your schema in a context
2. build a hook which provide an api for your tables

Example which is not perfect, but provide an [idea](https://codesandbox.io/s/react-with-crudodb-2-jvqn4)
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"format:prettier": "prettier --config .prettierrc --write",
"format:prettier:all": "yarn format:prettier \"**/*.+ts\"",
"format:prettier:all": "yarn format:prettier \"**/*.ts\"",
"semantic-release": "semantic-release",
"lint:es:fix": "yarn eslint --fix"
"lint:es:fix": "yarn eslint --fix",
"validate": "tsc --noEmit -p src/tsconfig.app.json"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -40,8 +41,10 @@
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-ban": "^1.5.1",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-typescript": "^0.14.0",
"eslint-plugin-unused-imports": "^1.0.0",
"fake-indexeddb": "^3.0.0",
Expand All @@ -50,7 +53,7 @@
"jest-preset-typescript": "^1.2.0",
"lint-staged": "^10.5.0",
"prettier": "^2.1.2",
"semantic-release": "^17.2.1",
"semantic-release": "^17.4.2",
"ts-jest": "^26.4.1",
"tslint": "^6.0.0",
"tslint-config-airbnb": "^5.11.2",
Expand Down
Loading