diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5704ca..4506f8d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,20 @@ This lib is fully documented and so you'll find detailed [migration guides](./MI ## 8.0.0-beta.3 (2019-02-07) +**A [full migration guide to version 8](./docs/MIGRATION_TO_V8.md) is available.** + ### Features +- The schema used for validation can (and should) be passed directly as the second argument fo `getItem()` - The returned type of `getItem()` is now inferred for basic types (`string`, `number`, `boolean`) -and arrays of basic types (`string[]`, `number[]`, `boolean[]`). -- Just use the new `JSONSchema` interface, IntelliSense will adjust itself based on the `type` option. +and arrays of basic types (`string[]`, `number[]`, `boolean[]`) +- Just use the new `JSONSchema` interface, IntelliSense will adjust itself based on the `type` option - `indexedDB` database and object store names default values are exported and can be changed (see the [interoperability guide](./docs/INTEROPERABILITY.md)) - `indexedDB` storage will now works in web workers too ### Breaking changes -A [full migration guide to version 8](./docs/MIGRATION_TO_V8.md) is available. - - `type` now required for array, object, const and enums validation schemas - `setItem()` and `setItemSubscribe()` no longer accept `null` or `undefined` when in `--strictNullChecks` - `JSONSchemaNull` removed @@ -26,6 +27,7 @@ A [full migration guide to version 8](./docs/MIGRATION_TO_V8.md) is available. ### Non-breaking changes - `JSONSchemaNumeric` deprecated +- `LSGetItemsOptions` deprecated (not necessary anymore) ### Reduced public API diff --git a/README.md b/README.md index de2f092a..8922b019 100644 --- a/README.md +++ b/README.md @@ -127,13 +127,10 @@ If you tried to store `undefined`, you'll get `null` too, as some storages don't Don't forget it's client-side storage: **always check the data**, as it could have been forged or deleted. -Starting with *version 5*, you can use a [JSON Schema](http://json-schema.org/) to validate the data. - -**Starting with *version 7*, validation is now required.** -A [migration guide](./docs/MIGRATION_TO_V7.md) is available. +You can use a [JSON Schema](http://json-schema.org/) to validate the data. **Starting with *version 7*, validation is now required.** ```typescript -this.localStorage.getItem('test', { schema: { type: 'string' } }) +this.localStorage.getItem('test', { type: 'string' }) .subscribe((user) => { // Called if data is valid or null }, (error) => { diff --git a/docs/MIGRATION_TO_V7.md b/docs/MIGRATION_TO_V7.md index 43de5e49..a6347fb3 100644 --- a/docs/MIGRATION_TO_V7.md +++ b/docs/MIGRATION_TO_V7.md @@ -83,7 +83,13 @@ So you ended up with a `string` type while the real data may not be a `string` a If you were not already validating your data, there are several options. -### Solution 1: JSON schema validation (recommended) +### Solution 1: JSON schema validation with v8 (recommended) + +Version 8 of the lib greatly simplifies validation. So if you're not yet on v7, +we strongly recommend you to to [upgrade to v8 directly](./MIGRATION_TO_V8.md), +and to follow the [new validation guide](./VALIDATION.md) instead. + +### Solution 2: JSON schema validation with v7 (quite painful) The simpler and better way to validate your data is to search `getItem` in your project and **use the JSON schema option proposed by the lib**. For example: @@ -96,9 +102,9 @@ this.localStorage.getItem('test', { schema: { type: 'string' } }) }); ``` -**A [full validation guide](./VALIDATION.md) is available with all the options.** +**A [full validation guide](./VALIDATION_BEFORE_V8.md) is available with all the options.** -### Solution 2: custom validation (painful) +### Solution 3: custom validation (very painful) You can use all the native JavaScript operators and functions to validate. For example: @@ -138,7 +144,7 @@ this.localStorage.getItem('test').subscribe((unsafeResult) => { }); ``` -### Solution 3: defer the upgrade (temporary) +### Solution 4: defer the upgrade (temporary) **Version 6 of the library is compatible with Angular 7.** So you can upgrade to Angular 7 now and defer the upgrade of this lib, @@ -147,7 +153,7 @@ to have some extra time to add validation. Of course, it should be a temporary solution as this scenario is *not* heavily tested, and as you'll miss new features and bug fixes. -### Solution 4: no validation (dirty) +### Solution 5: no validation (dirty) In some special scenarios, like development-only code, it could be painful to manage validation. @@ -163,6 +169,6 @@ as this method as been flagged as deprecated. ## More documentation - [Full changelog for v7](../CHANGELOG.md) -- [Full validation guide](./VALIDATION.md) +- [Full validation guide](./VALIDATION_BEFORE_V8.md) - [Other migration guides](../MIGRATION.md) - [Main documentation](../README.md) diff --git a/docs/MIGRATION_TO_V8.md b/docs/MIGRATION_TO_V8.md index 8d156993..ac41cfa5 100644 --- a/docs/MIGRATION_TO_V8.md +++ b/docs/MIGRATION_TO_V8.md @@ -24,6 +24,7 @@ npm install @ngx-pwa/local-storage@next ``` 2. Start your project: problems will be seen at compilation. +Or you could search for `getItem` as most breaking changes are about its options. ## The bad part: breaking changes @@ -41,12 +42,17 @@ Also, `JSONSchemaNull` is removed. Before v8, `type` was optional: ```typescript -this.localStorage.getItem('test', { schema: { items: { type: 'string' } } }) +this.localStorage.getItem('test', { schema: { + items: { type: 'string' } +} }) ``` Since v8: ```typescript -this.localStorage.getItem('test', { schema: { type: 'array', items: { type: 'string' } } }) +this.localStorage.getItem('test', { + type: 'array', + items: { type: 'string' } +}) ``` Also, `items` no longer accepts an array of JSON schemas, meaning arrays with multiple types @@ -58,16 +64,21 @@ are no longer possible (and it's better for consistency, use an object if you mi Before v8, `type` was optional: ```typescript -this.localStorage.getItem('test', { schema: { properties: { - test: { type: 'string' } -} } }) +this.localStorage.getItem('test', { schema: { + properties: { + test: { type: 'string' } + } +} }) ``` Since v8: ```typescript -this.localStorage.getItem('test', { schema: { type: 'object', properties: { - test: { type: 'string' } -} } }) +this.localStorage.getItem('test', { + type: 'object', + properties: { + test: { type: 'string' } + } +}) ``` ### Validation of constants @@ -82,7 +93,7 @@ this.localStorage.getItem('test', { schema: { const: 'value' } }) Since v8: ```typescript -this.localStorage.getItem('test', { schema: { type: 'string', const: 'value' } }) +this.localStorage.getItem('test', { type: 'string', const: 'value' }) ``` Also, `JSONSchemaConst` interface is removed. @@ -99,7 +110,7 @@ this.localStorage.getItem('test', { schema: { enum: ['value 1', 'value 2'] } }) Since v8: ```typescript -this.localStorage.getItem('test', { schema: { type: 'string', enum: ['value 1', 'value 2'] } }) +this.localStorage.getItem('test', { type: 'string', enum: ['value 1', 'value 2'] }) ``` It also means enums of different types are no longer possible (and it's better for consistency). @@ -119,15 +130,28 @@ While not recommended, you can still force it: this.localStorage.getItem('test', { schema: someSchema as any }) ``` - ## The good part: simplication changes -The following changes are not required. Previous code will still work, -but **for new code, follow these new guidelines**. +The following changes are not required but strongly recommended. +**Previous code will still work**, but *for new code, follow these new guidelines*. + +### Easier API for `getItem()` + +`getItem()` API has been simplified: the secong argument is now directly the schema for validation. + +Before v8: +```typescript +this.localStorage.getItem('test', { schema: { type: 'string' } }) +``` + +Since v8: +```typescript +this.localStorage.getItem('test', { type: 'string' }) +``` ### Cast now inferred! -The returned type of `getItem()` is now inferred for basic types (`string`, `number`, `boolean`) +The previous change allows that the returned type of `getItem()` is now inferred for basic types (`string`, `number`, `boolean`) and arrays of basic types (`string[]`, `number[]`, `boolean[]`). Before v8: @@ -143,21 +167,21 @@ this.localStorage.getItem('test', { schema: { type: 'string' } }).subscr Since v8: ```typescript -this.localStorage.getItem('test', { schema: { type: 'string' } }).subscribe((data) => { +this.localStorage.getItem('test', { type: 'string' }).subscribe((data) => { data; // string :D }); ``` -Cast is still needed for objects. Follow the [validation guide](./VALIDATION.md). +Cast is still needed for objects. Follow the new [validation guide](./VALIDATION.md). ### Use the generic `JSONSchema` -Now the `JSONSchema` interface has been refactored, just use this one, -and avoid the specific ones (`JSONSchemaString`, `JSONSchemaBoolean` and so on). +Now the `JSONSchema` interface has been refactored, just use this one. IntelliSense will adjust itself based on the `type` option. +The specific interfaces (`JSONSchemaString`, `JSONSchemaBoolean` and so on) are still there but useless. `JSONSchemaNumeric` still works but is deprecated in favor of `JSONSchemaNumber` or `JSONSchemaInteger` -(but again, just use `JSONSchema`) +(but again, just use `JSONSchema`). ## More documentation diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 71080079..f02ec798 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -32,55 +32,55 @@ as you'll see the more complex it is, the more complex is validation too. ### Boolean ```typescript -this.localStorage.getItem('test', { schema: { type: 'boolean' } }) +this.localStorage.getItem('test', { type: 'boolean' }) ``` ### Integer ```typescript -this.localStorage.getItem('test', { schema: { type: 'integer' } }) +this.localStorage.getItem('test', { type: 'integer' }) ``` ### Number ```typescript -this.localStorage.getItem('test', { schema: { type: 'number' } }) +this.localStorage.getItem('test', { type: 'number' }) ``` ### String ```typescript -this.localStorage.getItem('test', { schema: { type: 'string' } }) +this.localStorage.getItem('test', { type: 'string' }) ``` ### Arrays ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'array', items: { type: 'boolean' } -} }) +}) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'array', items: { type: 'integer' } -} }) +}) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'array', items: { type: 'number' } -} }) +}) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'array', items: { type: 'string' } -} }) +}) ``` What's expected in `items` is another JSON schema. @@ -107,7 +107,7 @@ const schema: JSONSchema = { required: ['firstName', 'lastName'] }; -this.localStorage.getItem('test', { schema }) +this.localStorage.getItem('test', schema) ``` What's expected for each property is another JSON schema. @@ -148,10 +148,10 @@ The lib can't check that. For example: ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'number', maximum: 5 -} }) +}) ``` ### Options for strings @@ -164,10 +164,10 @@ this.localStorage.getItem('test', { schema: { For example: ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'string', maxLength: 10 -} }) +}) ``` ### Options for arrays @@ -178,11 +178,11 @@ this.localStorage.getItem('test', { schema: { For example: ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { type: 'array', items: { type: 'string' }, maxItems: 5 -} }) +}) ``` ## How to validate nested types @@ -210,7 +210,7 @@ const schema: JSONSchema = { } }; -this.localStorage.getItem('test', { schema }) +this.localStorage.getItem('test', schema) ``` ## Errors vs. `null` @@ -218,7 +218,7 @@ this.localStorage.getItem('test', { schema }) If validation fails, it'll go in the error callback: ```typescript -this.localStorage.getItem('existing', { schema: { type: 'string' } }) +this.localStorage.getItem('existing', { type: 'string' }) .subscribe((result) => { // Called if data is valid or null }, (error) => { @@ -229,7 +229,7 @@ this.localStorage.getItem('existing', { schema: { type: 'string' } }) But as usual (like when you do a database request), not finding an item is not an error. It succeeds but returns `null`. ```typescript -this.localStorage.getItem('notExisting', { schema: { type: 'string' } }) +this.localStorage.getItem('notExisting', { type: 'string' }) .subscribe((result) => { result; // null }, (error) => { @@ -263,29 +263,4 @@ are *not* available in this lib: - `oneOf` - array for `type` -## ES6 shortcut - -In EcmaScript >= 6, this: - -```typescript -const schema: JSONSchemaBoolean = { type: 'boolean' }; - -this.localStorage.getItem('test', { schema }); -``` - -is a shortcut for this: -```typescript -const schema: JSONSchemaBoolean = { type: 'boolean' }; - -this.localStorage.getItem('test', { schema: schema }); -``` - -which works only if the property and the variable have the same name. -So if your variable has another name, you can't use the shortcut: -```typescript -const customSchema: JSONSchemaBoolean = { type: 'boolean' }; - -this.localStorage.getItem('test', { schema: customSchema }); -``` - [Back to general documentation](../README.md) diff --git a/projects/iframe/src/app/app.component.ts b/projects/iframe/src/app/app.component.ts index fb097ce2..807a6441 100644 --- a/projects/iframe/src/app/app.component.ts +++ b/projects/iframe/src/app/app.component.ts @@ -33,7 +33,7 @@ export class AppComponent implements OnInit { } }; - this.data$ = this.localStorage.setItem(key, { title: this.title }).pipe(switchMap(() => this.localStorage.getItem(key, { schema }))); + this.data$ = this.localStorage.setItem(key, { title: this.title }).pipe(switchMap(() => this.localStorage.getItem(key, schema))); } diff --git a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts index 8306fe64..c35178ae 100644 --- a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts @@ -3,7 +3,317 @@ import { MemoryDatabase } from './databases/memory-database'; import { JSONValidator } from './validation/json-validator'; import { JSONSchema, JSONSchemaArrayOf, JSONSchemaNumber } from './validation/json-schema'; -describe('getItem() overloads compilation', () => { +/* For now, `unknown` and `any` cases must be checked manually as any type can be converted to them. */ +// TODO: Find a way to automate this. + +describe('getItem() API v8', () => { + + let localStorageService: LocalStorage; + + beforeEach(() => { + + /* Do compilation tests on memory storage to avoid issues when other storages are not available */ + localStorageService = new LocalStorage(new MemoryDatabase(), new JSONValidator()); + + }); + + it('no schema / no cast', (done) => { + + localStorageService.getItem('test').subscribe((_: unknown) => { + + const t: unknown = _; + + expect().nothing(); + + done(); + + }); + + }); + + it('no schema / cast', (done) => { + + localStorageService.getItem('test').subscribe((_: unknown) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('literal basic schema / no cast', (done) => { + + localStorageService.getItem('test', { type: 'number' }).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('literal basic schema / cast', (done) => { + + localStorageService.getItem('test', { type: 'number' }).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('literal schema with options', (done) => { + + localStorageService.getItem('test', { type: 'number', maximum: 10 }).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('prepared schema with general interface', (done) => { + + const schema: JSONSchema = { type: 'number' }; + + localStorageService.getItem('test', schema).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('prepared schema with specific interface', (done) => { + + const schema: JSONSchemaNumber = { type: 'number' }; + + localStorageService.getItem('test', schema).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('string', (done) => { + + localStorageService.getItem('test', { type: 'string' }).subscribe((_: string | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('number', (done) => { + + localStorageService.getItem('test', { type: 'number' }).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('integer', (done) => { + + localStorageService.getItem('test', { type: 'integer' }).subscribe((_: number | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('boolean', (done) => { + + localStorageService.getItem('test', { type: 'boolean' }).subscribe((_: boolean | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array of strings', (done) => { + + localStorageService.getItem('test', { + type: 'array', + items: { type: 'string' } + }).subscribe((_: string[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array of numbers', (done) => { + + localStorageService.getItem('test', { + type: 'array', + items: { type: 'number' } + }).subscribe((_: number[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array of integers', (done) => { + + localStorageService.getItem('test', { + type: 'array', + items: { type: 'integer' } + }).subscribe((_: number[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array of booleans', (done) => { + + localStorageService.getItem('test', { + type: 'array', + items: { type: 'boolean' } + }).subscribe((_: boolean[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array with extra options', (done) => { + + const schema: JSONSchemaArrayOf = { + type: 'array', + items: { type: 'number' }, + maxItems: 5 + }; + + localStorageService.getItem('test', schema).subscribe((_: number[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('array of objects', (done) => { + + interface Test { + test: string; + } + + localStorageService.getItem('test', { + type: 'array', + items: { + type: 'object', + properties: { + test: { type: 'string' } + } + } + }).subscribe((_: Test[] | null) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('objects / cast / no schema', (done) => { + + interface Test { + test: string; + } + + localStorageService.getItem('test').subscribe((_: unknown) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('objects / no cast / schema', (done) => { + + localStorageService.getItem('test', { + type: 'object', + properties: { + test: { type: 'string' } + } + }).subscribe((_: any) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('objects / cast / schema', (done) => { + + interface Test { + test: string; + } + + localStorageService.getItem('test', { + type: 'object', + properties: { + test: { type: 'string' } + } + }).subscribe((_: Test | null) => { + + expect().nothing(); + + done(); + + }); + + }); + +}); + +describe('getItem() API v7', () => { let localStorageService: LocalStorage; @@ -16,7 +326,7 @@ describe('getItem() overloads compilation', () => { it('no schema / no cast', (done) => { - localStorageService.getItem('test').subscribe((_) => { + localStorageService.getItem('test').subscribe((_: unknown) => { expect().nothing(); @@ -28,7 +338,7 @@ describe('getItem() overloads compilation', () => { it('no schema / cast', (done) => { - localStorageService.getItem('test').subscribe((_) => { + localStorageService.getItem('test').subscribe((_: unknown) => { expect().nothing(); @@ -40,7 +350,7 @@ describe('getItem() overloads compilation', () => { it('literal basic schema / no cast', (done) => { - localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_: any) => { expect().nothing(); @@ -52,7 +362,7 @@ describe('getItem() overloads compilation', () => { it('literal basic schema / cast', (done) => { - localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_: any) => { expect().nothing(); @@ -64,7 +374,7 @@ describe('getItem() overloads compilation', () => { it('literal schema with options', (done) => { - localStorageService.getItem('test', { schema: { type: 'number', maximum: 10 } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'number', maximum: 10 } }).subscribe((_: any) => { expect().nothing(); @@ -78,7 +388,7 @@ describe('getItem() overloads compilation', () => { const schema: JSONSchema = { type: 'number' }; - localStorageService.getItem('test', { schema }).subscribe((_) => { + localStorageService.getItem('test', { schema }).subscribe((_: any) => { expect().nothing(); @@ -92,7 +402,7 @@ describe('getItem() overloads compilation', () => { const schema: JSONSchemaNumber = { type: 'number' }; - localStorageService.getItem('test', { schema }).subscribe((_) => { + localStorageService.getItem('test', { schema }).subscribe((_: any) => { expect().nothing(); @@ -104,7 +414,7 @@ describe('getItem() overloads compilation', () => { it('string', (done) => { - localStorageService.getItem('test', { schema: { type: 'string' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'string' } }).subscribe((_: any) => { expect().nothing(); @@ -116,7 +426,7 @@ describe('getItem() overloads compilation', () => { it('number', (done) => { - localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_: any) => { expect().nothing(); @@ -128,7 +438,7 @@ describe('getItem() overloads compilation', () => { it('integer', (done) => { - localStorageService.getItem('test', { schema: { type: 'integer' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'integer' } }).subscribe((_: any) => { expect().nothing(); @@ -140,7 +450,7 @@ describe('getItem() overloads compilation', () => { it('boolean', (done) => { - localStorageService.getItem('test', { schema: { type: 'boolean' } }).subscribe((_) => { + localStorageService.getItem('test', { schema: { type: 'boolean' } }).subscribe((_: any) => { expect().nothing(); @@ -155,7 +465,7 @@ describe('getItem() overloads compilation', () => { localStorageService.getItem('test', { schema: { type: 'array', items: { type: 'string' } - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); @@ -170,7 +480,7 @@ describe('getItem() overloads compilation', () => { localStorageService.getItem('test', { schema: { type: 'array', items: { type: 'number' } - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); @@ -185,7 +495,7 @@ describe('getItem() overloads compilation', () => { localStorageService.getItem('test', { schema: { type: 'array', items: { type: 'integer' } - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); @@ -200,7 +510,7 @@ describe('getItem() overloads compilation', () => { localStorageService.getItem('test', { schema: { type: 'array', items: { type: 'boolean' } - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); @@ -218,7 +528,7 @@ describe('getItem() overloads compilation', () => { maxItems: 5 }; - localStorageService.getItem('test', { schema }).subscribe((_) => { + localStorageService.getItem('test', { schema }).subscribe((_: any) => { expect().nothing(); @@ -242,7 +552,7 @@ describe('getItem() overloads compilation', () => { test: { type: 'string' } } } - } }).subscribe((_) => { + } }).subscribe((_: Test[] | null) => { expect().nothing(); @@ -258,7 +568,7 @@ describe('getItem() overloads compilation', () => { test: string; } - localStorageService.getItem('test').subscribe((_) => { + localStorageService.getItem('test').subscribe((_: unknown) => { expect().nothing(); @@ -275,7 +585,7 @@ describe('getItem() overloads compilation', () => { properties: { test: { type: 'string' } } - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); @@ -296,7 +606,7 @@ describe('getItem() overloads compilation', () => { properties: { test: { type: 'string' } } - } }).subscribe((_) => { + } }).subscribe((_: Test | null) => { expect().nothing(); @@ -315,7 +625,7 @@ describe('getItem() overloads compilation', () => { test: { type: 'string' } }, ddd: 'ddd' - } }).subscribe((_) => { + } }).subscribe((_: any) => { expect().nothing(); diff --git a/projects/ngx-pwa/local-storage/src/lib/interoperability.spec.ts b/projects/ngx-pwa/local-storage/src/lib/interoperability.spec.ts index 46b17c1f..0c30b7c5 100644 --- a/projects/ngx-pwa/local-storage/src/lib/interoperability.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/interoperability.spec.ts @@ -77,7 +77,9 @@ function testGetCompatibilityWithNativeAPI(localStorageService: LocalStorage, do store.add(value, index).addEventListener('success', () => { - localStorageService.getItem(index, { schema }).subscribe((result) => { + const request = schema ? localStorageService.getItem(index, schema) : localStorageService.getItem(index); + + request.subscribe((result) => { expect(result).toEqual((value !== undefined) ? value : null); diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts index 13b854d0..e6f2e649 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts @@ -400,43 +400,93 @@ function tests(localStorageService: LocalStorage) { required: ['expected'] }; - it('valid', (done) => { + describe('API v8', () => { - const value = { expected: 'value' }; + it('valid', (done) => { - localStorageService.setItem(key, value).pipe( - mergeMap(() => localStorageService.getItem(key, { schema })) - ).subscribe((data) => { + const value = { expected: 'value' }; - expect(data).toEqual(value); + localStorageService.setItem(key, value).pipe( + mergeMap(() => localStorageService.getItem(key, schema)) + ).subscribe((data) => { - done(); + expect(data).toEqual(value); + + done(); + + }); }); - }); + it('invalid', (done) => { - it('invalid', (done) => { + localStorageService.setItem(key, 'test').pipe( + mergeMap(() => localStorageService.getItem(key, schema)) + ).subscribe({ error: (error) => { - localStorageService.setItem(key, 'test').pipe( - mergeMap(() => localStorageService.getItem(key, { schema })) - ).subscribe({ error: (error) => { + expect(error.message).toBe(VALIDATION_ERROR); + + done(); + + } }); - expect(error.message).toBe(VALIDATION_ERROR); + }); + + it('null: no validation', (done) => { + + localStorageService.getItem<{ expected: string }>(`noassociateddata${Date.now()}`, schema).subscribe(() => { + + expect().nothing(); done(); - } }); + }); + + }); }); - it('null: no validation', (done) => { + describe('API v7', () => { - localStorageService.getItem<{ expected: string }>(`noassociateddata${Date.now()}`, { schema }).subscribe(() => { + it('valid', (done) => { - expect().nothing(); + const value = { expected: 'value' }; - done(); + localStorageService.setItem(key, value).pipe( + mergeMap(() => localStorageService.getItem(key, { schema })) + ).subscribe((data) => { + + expect(data).toEqual(value); + + done(); + + }); + + }); + + it('invalid', (done) => { + + localStorageService.setItem(key, 'test').pipe( + mergeMap(() => localStorageService.getItem(key, { schema })) + ).subscribe({ error: (error) => { + + expect(error.message).toBe(VALIDATION_ERROR); + + done(); + + } }); + + }); + + it('null: no validation', (done) => { + + localStorageService.getItem<{ expected: string }>(`noassociateddata${Date.now()}`, { schema }).subscribe(() => { + + expect().nothing(); + + done(); + + }); }); diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts index ab5b20b8..a450dd02 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts @@ -3,22 +3,27 @@ import { Observable, throwError, of, OperatorFunction } from 'rxjs'; import { mergeMap, catchError } from 'rxjs/operators'; import { LocalDatabase } from './databases/local-database'; +import { LocalStorageDatabase } from './databases/localstorage-database'; +import { JSONValidator } from './validation/json-validator'; import { JSONSchema, JSONSchemaBoolean, JSONSchemaInteger, JSONSchemaNumber, JSONSchemaString, JSONSchemaArrayOf } from './validation/json-schema'; -import { JSONValidator } from './validation/json-validator'; import { IDB_BROKEN_ERROR, ValidationError } from './exceptions'; -import { LocalStorageDatabase } from './databases/localstorage-database'; import { PREFIX } from './tokens'; +/** + * @deprecated Will be removed in v9 + */ export interface LSGetItemOptions { + /** * Subset of the JSON Schema standard. * Types are enforced to validate everything: each value **must** have a `type`. * @see https://github.com/cyrilletuzi/angular-async-local-storage/blob/master/docs/VALIDATION.md */ schema?: JSONSchema | null; + } @Injectable({ @@ -35,13 +40,6 @@ export class LocalStorage { } - /** - * Default options for `getItem()` - */ - private readonly getItemOptionsDefault: LSGetItemOptions = { - schema: null, - }; - /** * Constructor params are provided by Angular (but can also be passed manually in tests) * @param database Storage to use @@ -61,21 +59,15 @@ export class LocalStorage { * @param key The item's key * @returns The item's value if the key exists, `null` otherwise, wrapped in a RxJS `Observable` */ - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaString }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaInteger | JSONSchemaNumber }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaBoolean }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaArrayOf }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaArrayOf }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaArrayOf }): Observable; - getItem(key: string, options: LSGetItemOptions & { schema: JSONSchema }): Observable; - getItem(key: string, options?: LSGetItemOptions): Observable; - getItem(key: string, options = this.getItemOptionsDefault) { + getItem(key: string, schema: JSONSchemaString): Observable; + getItem(key: string, schema: JSONSchemaInteger | JSONSchemaNumber): Observable; + getItem(key: string, schema: JSONSchemaBoolean): Observable; + getItem(key: string, schema: JSONSchemaArrayOf): Observable; + getItem(key: string, schema: JSONSchemaArrayOf): Observable; + getItem(key: string, schema: JSONSchemaArrayOf): Observable; + getItem(key: string, schema: JSONSchema | { schema: JSONSchema }): Observable; + getItem(key: string, schema?: null): Observable; + getItem(key: string, schema: JSONSchema | { schema: JSONSchema } | null = null) { /* Get the data in storage */ return this.database.getItem(key).pipe( @@ -88,10 +80,13 @@ export class LocalStorage { /* No need to validate if the data is `null` */ return of(null); - } else if (options.schema) { + } else if (schema) { + + /* Backward compatibility with version <= 7 */ + const schemaFinal: JSONSchema = ('schema' in schema) ? schema.schema : schema; /* Validate data against a JSON schema if provied */ - if (!this.jsonValidator.validate(data, options.schema)) { + if (!this.jsonValidator.validate(data, schemaFinal)) { return throwError(new ValidationError()); } diff --git a/projects/ngx-pwa/local-storage/src/public_api.ts b/projects/ngx-pwa/local-storage/src/public_api.ts index 32c192e4..a9675e13 100644 --- a/projects/ngx-pwa/local-storage/src/public_api.ts +++ b/projects/ngx-pwa/local-storage/src/public_api.ts @@ -7,6 +7,6 @@ export { JSONSchemaNumeric, JSONSchemaString, JSONSchemaArray, JSONSchemaArrayOf, JSONSchemaObject } from './lib/validation/json-schema'; export { LocalDatabase } from './lib/databases/local-database'; -export { LSGetItemOptions, LocalStorage } from './lib/lib.service'; +export { LocalStorage, LSGetItemOptions } from './lib/lib.service'; export { localStorageProviders, LocalStorageProvidersConfig, DEFAULT_IDB_DB_NAME, DEFAULT_IDB_STORE_NAME } from './lib/tokens'; export { VALIDATION_ERROR, ValidationError } from './lib/exceptions'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 47039b86..b9541128 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -34,7 +34,7 @@ export class AppComponent implements OnInit { } }; - this.data$ = this.localStorage.setItem(key, { title: this.title }).pipe(switchMap(() => this.localStorage.getItem(key, { schema }))); + this.data$ = this.localStorage.setItem(key, { title: this.title }).pipe(switchMap(() => this.localStorage.getItem(key, schema))); }