-
-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(strongbox): create @appium/strongbox
This PR creates a new package `@appium/strongbox`, which provides a generic persistence store for Appium extensions.
- Loading branch information
Showing
12 changed files
with
747 additions
and
4 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,96 @@ | ||
# @appium/strongbox | ||
|
||
> Persistent storage for Appium extensions | ||
## Summary | ||
|
||
This package is intended to be used in [Appium](https://appium.io) extensions which need to persist data between Appium runs. An example of such data may be a device token or key. | ||
|
||
`@appium/strongbox` provides a simple and extensible API for managing such data, while abstracting the underlying storage mechanism. | ||
|
||
_Note:_ This module is not intended for storing sensitive data. | ||
|
||
## Usage | ||
|
||
First, create an instance of `Strongbox`: | ||
|
||
```ts | ||
import {strongbox} from '@appium/strongbox'; | ||
|
||
const box = strongbox('my-pkg'); | ||
``` | ||
|
||
This instance corresponds to a unique collection of data. | ||
|
||
From here, create a placeholder for data (you will need to provide the type of data you intend to store): | ||
|
||
```ts | ||
const item = await box.createItem<string>('my unique name'); | ||
``` | ||
|
||
...or, if you already have the data on-hand: | ||
|
||
```ts | ||
const item: Buffer|string = getSomeData(); | ||
|
||
const item = await box.createItemWithContents('my unique name', data); | ||
``` | ||
|
||
Either way, you can read its contents: | ||
|
||
```ts | ||
// if the item doesn't exist, this result will be undefined | ||
const contents = await item.read(); | ||
``` | ||
|
||
Or write new data to the item: | ||
|
||
```ts | ||
await item.write('new stuff'); | ||
``` | ||
|
||
The last-read contents of the `Item` will be available on the `contents` property, but the value of this property is only current as of the last `read()`: | ||
|
||
```ts | ||
const {contents} = item; | ||
``` | ||
|
||
## API | ||
|
||
In lieu of actual documentation, look at the type definitions that this package ships. | ||
|
||
## Customization | ||
|
||
1. Create a class that implements the `Item` interface: | ||
|
||
```ts | ||
import {strongbox, Item} from '@appium/strongbox'; | ||
import {Foo, getFoo} from 'somewhere/else'; | ||
|
||
class FooItem implements Item<Foo> { | ||
// ... | ||
} | ||
``` | ||
|
||
2. Provide this class as the `defaultCtor` option to `strongbox()`: | ||
|
||
```ts | ||
const box = strongbox('my-pkg', {defaultCtor: FooItem}); | ||
``` | ||
|
||
3. Use like you would any other `Strongbox` instance: | ||
|
||
```ts | ||
const foo: Foo = getFoo(); | ||
const item = await box.createItemWithValue('my unique name', Foo); | ||
``` | ||
|
||
## Default Behavior, For the Curious | ||
|
||
Out-of-the-box, a `Strongbox` instance corresponds to a directory on-disk, and each `Item` (returned by `createItem()/createItemWithContents()`) corresponds to a file within that directory. | ||
|
||
The directory of the `Strongbox` instance is determined by the [env-paths](https://www.npmjs.com/package/env-paths) package, and is platform-specific. | ||
|
||
## License | ||
|
||
Copyright © 2023 OpenJS Foundation. Licensed Apache-2.0 |
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,95 @@ | ||
import {mkdir, readFile, unlink, writeFile} from 'node:fs/promises'; | ||
import path from 'node:path'; | ||
import type {Item, ItemEncoding, Value} from '.'; | ||
import {slugify} from './util'; | ||
|
||
/** | ||
* Base item implementation | ||
* | ||
* @remarks This class is not intended to be instantiated directly | ||
* @typeParam T - Type of data stored in the `Item` | ||
*/ | ||
export class BaseItem<T extends Value> implements Item<T> { | ||
/** | ||
* {@inheritdoc Item.value} | ||
*/ | ||
protected _value?: T | undefined; | ||
|
||
/** | ||
* Unique slugified identifier | ||
*/ | ||
public readonly id: string; | ||
|
||
/** | ||
* {@inheritdoc Item.value} | ||
*/ | ||
public readonly value: T | undefined; | ||
|
||
/** | ||
* Slugifies the name | ||
* @param name Name of instance | ||
* @param container Slugified name of container | ||
* @param encoding Defaults to `utf8` | ||
*/ | ||
constructor( | ||
public readonly name: string, | ||
public readonly container: string, | ||
public readonly encoding: ItemEncoding = 'utf8' | ||
) { | ||
this.id = path.join(container, slugify(name)); | ||
|
||
Object.defineProperties(this, { | ||
value: { | ||
get() { | ||
return this._value; | ||
}, | ||
enumerable: true, | ||
}, | ||
_value: { | ||
enumerable: false, | ||
writable: true, | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* {@inheritdoc Item.read} | ||
*/ | ||
public async read(): Promise<T | undefined> { | ||
try { | ||
this._value = (await readFile(this.id, { | ||
encoding: this.encoding, | ||
})) as T; | ||
} catch (e) { | ||
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { | ||
throw e; | ||
} | ||
} | ||
return this._value; | ||
} | ||
|
||
/** | ||
* {@inheritdoc Item.write} | ||
*/ | ||
public async write(value: T): Promise<void> { | ||
if (this._value !== value) { | ||
await mkdir(path.dirname(this.id), {recursive: true}); | ||
await writeFile(this.id, value, this.encoding); | ||
this._value = value; | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc Item.clear} | ||
*/ | ||
public async clear(): Promise<void> { | ||
try { | ||
await unlink(this.id); | ||
this._value = undefined; | ||
} catch (e) { | ||
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { | ||
throw e; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.