@toa.io/extensions.stash 0.20.0-dev.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +32 -0
- package/readme.md +61 -0
- package/source/Aspect.fixtures.ts +3 -0
- package/source/Aspect.test.ts +103 -0
- package/source/Aspect.ts +58 -0
- package/source/Connection.fixtures.ts +18 -0
- package/source/Connection.test.ts +51 -0
- package/source/Connection.ts +19 -0
- package/source/extension.ts +46 -0
- package/source/index.ts +1 -0
- package/transpiled/Aspect.d.ts +17 -0
- package/transpiled/Aspect.fixtures.d.ts +4 -0
- package/transpiled/Aspect.fixtures.js +7 -0
- package/transpiled/Aspect.fixtures.js.map +1 -0
- package/transpiled/Aspect.js +49 -0
- package/transpiled/Aspect.js.map +1 -0
- package/transpiled/Aspect.test.d.ts +1 -0
- package/transpiled/Aspect.test.js +102 -0
- package/transpiled/Aspect.test.js.map +1 -0
- package/transpiled/Connection.d.ts +7 -0
- package/transpiled/Connection.fixtures.d.ts +10 -0
- package/transpiled/Connection.fixtures.js +18 -0
- package/transpiled/Connection.fixtures.js.map +1 -0
- package/transpiled/Connection.js +19 -0
- package/transpiled/Connection.js.map +1 -0
- package/transpiled/Connection.test.d.ts +1 -0
- package/transpiled/Connection.test.js +40 -0
- package/transpiled/Connection.test.js.map +1 -0
- package/transpiled/extension.d.ts +12 -0
- package/transpiled/extension.js +35 -0
- package/transpiled/extension.js.map +1 -0
- package/transpiled/index.d.ts +1 -0
- package/transpiled/index.js +18 -0
- package/transpiled/index.js.map +1 -0
- package/tsconfig.json +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toa.io/extensions.stash",
|
|
3
|
+
"version": "0.20.0-dev.18",
|
|
4
|
+
"description": "Toa Stash",
|
|
5
|
+
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
|
+
"homepage": "https://github.com/toa-io/toa#readme",
|
|
7
|
+
"main": "transpiled/index.js",
|
|
8
|
+
"types": "transpiled/index.d.ts",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/toa-io/toa.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/toa-io/toa/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"ioredis": "5.3.2",
|
|
21
|
+
"msgpackr": "1.9.5",
|
|
22
|
+
"redlock": "5.0.0-beta.2"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"prepublishOnly": "npm run transpile",
|
|
26
|
+
"transpile": "tsc"
|
|
27
|
+
},
|
|
28
|
+
"jest": {
|
|
29
|
+
"preset": "ts-jest",
|
|
30
|
+
"testEnvironment": "node"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Toa Stash
|
|
2
|
+
|
|
3
|
+
Shared cache and distributed lock manager on top of [ioredis](https://github.com/redis/ioredis)
|
|
4
|
+
and [redlock](https://github.com/mike-marcacci/node-redlock).
|
|
5
|
+
|
|
6
|
+
## Shared cache
|
|
7
|
+
|
|
8
|
+
`stash` aspect exposes [`ioredis` methods](https://redis.github.io/ioredis/classes/Cluster.html).
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
async function computation (input, context) {
|
|
12
|
+
await context.stash.set('key', 'value')
|
|
13
|
+
await context.stash.get('key')
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Keys are component-scoped, meaning that the underlying Redis keys are `namespace:name:key`.
|
|
18
|
+
|
|
19
|
+
### Storing objects
|
|
20
|
+
|
|
21
|
+
`async store (key: string, value: object, ...args: Array<string | number>)`
|
|
22
|
+
|
|
23
|
+
`async fetch (key: string): object`
|
|
24
|
+
|
|
25
|
+
`...args` are the [arguments of `set`](https://redis.github.io/ioredis/classes/Cluster.html#set)
|
|
26
|
+
starting from third.
|
|
27
|
+
|
|
28
|
+
Values are encoded using [msgpack](https://msgpack.org).
|
|
29
|
+
|
|
30
|
+
## Distributed lock manager
|
|
31
|
+
|
|
32
|
+
`async lock(id: string | string[], routine: async? () => void)`
|
|
33
|
+
|
|
34
|
+
Executes `routine` once a lock is succesively acquired. Lock ID is component-scoped.
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
async function computation (input, context) {
|
|
38
|
+
await context.stash.lock('lock id', () => console.log('Lock acquired'))
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Manifest
|
|
43
|
+
|
|
44
|
+
To enable extension for a component, add `null` definition to its manifest:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
stash: ~
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Deployment
|
|
51
|
+
|
|
52
|
+
`stash` context annotation is a [Pointer](/libraries/pointer)-like.
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
stash: # shortcut is available
|
|
56
|
+
default: redis://redis.example.com
|
|
57
|
+
dummies.dummy: redis://dummies.redis.example.com
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Neither Password Authentication nor TLS are implemented.
|
|
61
|
+
[#367](https://github.com/toa-io/toa/issues/367)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import Redlock from 'redlock'
|
|
2
|
+
import { encode } from 'msgpackr'
|
|
3
|
+
import { generate } from 'randomstring'
|
|
4
|
+
import { Aspect } from './Aspect'
|
|
5
|
+
import * as mock from './Connection.fixtures'
|
|
6
|
+
import type { Connection } from './Connection'
|
|
7
|
+
import type { Cluster } from 'ioredis'
|
|
8
|
+
|
|
9
|
+
jest.mock('redlock')
|
|
10
|
+
|
|
11
|
+
let aspect: Aspect
|
|
12
|
+
let connection: jest.MockedObject<Connection>
|
|
13
|
+
let redis: jest.MockedObject<Cluster>
|
|
14
|
+
let redlock: jest.MockedObject<Redlock>
|
|
15
|
+
|
|
16
|
+
const key = generate()
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks()
|
|
20
|
+
|
|
21
|
+
connection = new mock.Connection() as unknown as jest.MockedObject<Connection>
|
|
22
|
+
redis = connection.redis as unknown as jest.MockedObject<Cluster>
|
|
23
|
+
aspect = new Aspect(connection)
|
|
24
|
+
const m = (Redlock as unknown as jest.Mock<Redlock>).mock
|
|
25
|
+
redlock = (Redlock as unknown as jest.Mock<Redlock>).mock.instances[0] as unknown as jest.MockedObject<Redlock>
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should depend on Connection', async () => {
|
|
29
|
+
expect(connection.link).toHaveBeenCalledWith(aspect)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should set', async () => {
|
|
33
|
+
const value = generate()
|
|
34
|
+
|
|
35
|
+
await aspect.invoke('set', key, value)
|
|
36
|
+
|
|
37
|
+
expect(redis.set).toHaveBeenCalledWith(key, value)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should get', async () => {
|
|
41
|
+
const value = await aspect.invoke('get', key)
|
|
42
|
+
|
|
43
|
+
expect(redis.get).toHaveBeenCalledWith(key)
|
|
44
|
+
expect(value).toStrictEqual(await redis.get.mock.results[0].value)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('buffers', () => {
|
|
48
|
+
it.each([
|
|
49
|
+
['object', { [generate()]: generate() }],
|
|
50
|
+
['array', [generate(), generate()]]
|
|
51
|
+
])('should encode %s', async (_, value) => {
|
|
52
|
+
await aspect.invoke('store', key, value)
|
|
53
|
+
|
|
54
|
+
const buffer = encode(value)
|
|
55
|
+
|
|
56
|
+
expect(redis.set).toHaveBeenCalledWith(key, buffer)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should pass arguments', async () => {
|
|
60
|
+
const value = [1, 2]
|
|
61
|
+
const buffer = encode(value)
|
|
62
|
+
|
|
63
|
+
await aspect.invoke('store', key, value, 'EX', 1000)
|
|
64
|
+
|
|
65
|
+
expect(redis.set).toHaveBeenCalledWith(key, buffer, 'EX', 1000)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it.each([
|
|
69
|
+
['object', { [generate()]: generate() }],
|
|
70
|
+
['array', [generate(), generate()]]
|
|
71
|
+
])('should decode %s', async (_, value) => {
|
|
72
|
+
const buffer = encode(value)
|
|
73
|
+
|
|
74
|
+
redis.getBuffer.mockImplementationOnce(async () => buffer)
|
|
75
|
+
|
|
76
|
+
const output = await aspect.invoke('fetch', key)
|
|
77
|
+
|
|
78
|
+
expect(redis.getBuffer).toHaveBeenCalledWith(key)
|
|
79
|
+
expect(output).toStrictEqual(value)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should create DLM', async () => {
|
|
84
|
+
expect(Redlock).toHaveBeenCalledWith([redis])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should acquire lock', async () => {
|
|
88
|
+
const keys = [generate(), generate()]
|
|
89
|
+
const callback = jest.fn()
|
|
90
|
+
|
|
91
|
+
await aspect.invoke('lock', keys, callback)
|
|
92
|
+
|
|
93
|
+
expect(redlock.using).toHaveBeenCalledWith(keys, 5000, callback)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should handle non-array key', async () => {
|
|
97
|
+
const key = generate()
|
|
98
|
+
const callback = jest.fn()
|
|
99
|
+
|
|
100
|
+
await aspect.invoke('lock', key, callback)
|
|
101
|
+
|
|
102
|
+
expect(redlock.using).toHaveBeenCalledWith([key], 5000, callback)
|
|
103
|
+
})
|
package/source/Aspect.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Redlock from 'redlock'
|
|
2
|
+
import { encode, decode } from 'msgpackr'
|
|
3
|
+
import { Connector, type extensions } from '@toa.io/core'
|
|
4
|
+
import type { Connection } from './Connection'
|
|
5
|
+
import type { Redis } from 'ioredis'
|
|
6
|
+
|
|
7
|
+
export class Aspect extends Connector implements extensions.Aspect {
|
|
8
|
+
public readonly name = 'stash'
|
|
9
|
+
private readonly redis: Redis
|
|
10
|
+
private readonly redlock: Redlock
|
|
11
|
+
|
|
12
|
+
public constructor (connection: Connection) {
|
|
13
|
+
super()
|
|
14
|
+
|
|
15
|
+
this.redis = connection.redis
|
|
16
|
+
this.redlock = new Redlock([this.redis])
|
|
17
|
+
|
|
18
|
+
this.depends(connection)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async invoke (method: 'store', key: string, value: object): Promise<void>
|
|
22
|
+
public async invoke (method: 'fetch', key: string): Promise<object>
|
|
23
|
+
public async invoke (method: 'lock', key: string | string[], routine: Routine): Promise<void>
|
|
24
|
+
public async invoke (method: string, ...args: unknown[]): Promise<any>
|
|
25
|
+
public async invoke (method: string, ...args: unknown[]): Promise<any> {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
27
|
+
// @ts-expect-error
|
|
28
|
+
if (typeof this.redis[method] === 'function') return this.redis[method](...args)
|
|
29
|
+
|
|
30
|
+
if (method === 'store') await this.store(args[0] as string, args[1] as object, ...args.slice(2))
|
|
31
|
+
|
|
32
|
+
if (method === 'fetch') return await this.fetch(args[0] as string)
|
|
33
|
+
|
|
34
|
+
if (method === 'lock') await this.lock(args[0] as string, args[1] as () => Promise<void>)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async store (key: string, value: object, ...args: unknown[]): Promise<void> {
|
|
38
|
+
const buffer = encode(value)
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
41
|
+
// @ts-expect-error
|
|
42
|
+
await this.redis.set(key, buffer, ...args)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async fetch (key: string): Promise<object | null> {
|
|
46
|
+
const buffer = await this.redis.getBuffer(key)
|
|
47
|
+
|
|
48
|
+
return buffer === null ? null : decode(buffer)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async lock (key: string | string[], routine: Routine): Promise<void> {
|
|
52
|
+
if (typeof key === 'string') key = [key]
|
|
53
|
+
|
|
54
|
+
await this.redlock.using(key, 5000, routine)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type Routine = () => Promise<void>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generate } from 'randomstring'
|
|
2
|
+
import type * as redis from 'ioredis'
|
|
3
|
+
|
|
4
|
+
export const Redis = jest.fn(() => ({
|
|
5
|
+
connect: jest.fn(async () => undefined),
|
|
6
|
+
disconnect: jest.fn(),
|
|
7
|
+
get: jest.fn(async () => generate()),
|
|
8
|
+
getBuffer: jest.fn(async () => Buffer.from(generate())),
|
|
9
|
+
set: jest.fn(async () => undefined),
|
|
10
|
+
on: jest.fn()
|
|
11
|
+
})) as unknown as jest.Mock<typeof redis.Cluster>
|
|
12
|
+
|
|
13
|
+
export const Connection = jest.fn(() => ({
|
|
14
|
+
redis: new Redis(),
|
|
15
|
+
link: jest.fn()
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
export const ioredis = { Redis }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { generate } from 'randomstring'
|
|
2
|
+
import { Connection } from './Connection'
|
|
3
|
+
import { Connector, type Locator } from '@toa.io/core'
|
|
4
|
+
import { Redis } from './Connection.fixtures'
|
|
5
|
+
import type * as redis from 'ioredis'
|
|
6
|
+
|
|
7
|
+
jest.mock('ioredis', () => ({
|
|
8
|
+
Redis: jest.fn((nodes: string[], options: redis.ClusterOptions) => new Redis(nodes, options))
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
let connection: Connection
|
|
12
|
+
let cluster: jest.MockedObject<redis.Cluster>
|
|
13
|
+
|
|
14
|
+
const url = generate()
|
|
15
|
+
|
|
16
|
+
const locator = {
|
|
17
|
+
namespace: generate(),
|
|
18
|
+
name: generate()
|
|
19
|
+
} as unknown as Locator
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks()
|
|
23
|
+
|
|
24
|
+
connection = new Connection(url, locator)
|
|
25
|
+
cluster = Redis.mock.results[0].value
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should be instance of Connector', async () => {
|
|
29
|
+
expect(connection).toBeInstanceOf(Connector)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should connect', async () => {
|
|
33
|
+
const keyPrefix = `${locator.namespace}:${locator.name}:`
|
|
34
|
+
const options: redis.ClusterOptions = { keyPrefix, enableReadyCheck: true }
|
|
35
|
+
|
|
36
|
+
expect(Redis).toHaveBeenCalledWith(url, options)
|
|
37
|
+
|
|
38
|
+
// await connection.connect()
|
|
39
|
+
//
|
|
40
|
+
// expect(cluster.connect).toHaveBeenCalled()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should disconnect', async () => {
|
|
44
|
+
await connection.disconnect()
|
|
45
|
+
|
|
46
|
+
expect(cluster.disconnect).toHaveBeenCalled()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should expose cluster', async () => {
|
|
50
|
+
expect(connection.redis).toStrictEqual(cluster)
|
|
51
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Redis, type ClusterOptions } from 'ioredis'
|
|
2
|
+
import { Connector, type Locator } from '@toa.io/core'
|
|
3
|
+
|
|
4
|
+
export class Connection extends Connector {
|
|
5
|
+
public readonly redis: Redis
|
|
6
|
+
|
|
7
|
+
public constructor (url: string, locator: Locator) {
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
const keyPrefix = `${locator.namespace}:${locator.name}:`
|
|
11
|
+
const options: ClusterOptions = { keyPrefix, enableReadyCheck: true }
|
|
12
|
+
|
|
13
|
+
this.redis = new Redis(url, options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected override async close (): Promise<void> {
|
|
17
|
+
this.redis.disconnect()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { uris } from '@toa.io/pointer'
|
|
2
|
+
import { Aspect } from './Aspect'
|
|
3
|
+
import { Connection } from './Connection'
|
|
4
|
+
import type { Locator, extensions } from '@toa.io/core'
|
|
5
|
+
import type { context } from '@toa.io/norm'
|
|
6
|
+
import type * as operations from '@toa.io/operations'
|
|
7
|
+
|
|
8
|
+
export class Factory implements extensions.Factory {
|
|
9
|
+
public aspect (locator: Locator): extensions.Aspect {
|
|
10
|
+
const url = this.url()
|
|
11
|
+
const connection = new Connection(url, locator)
|
|
12
|
+
|
|
13
|
+
return new Aspect(connection)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private url (): string {
|
|
17
|
+
if (process.env.TOA_DEV === '1') return 'redis://localhost'
|
|
18
|
+
|
|
19
|
+
const url = process.env[URL_VARIABLE]
|
|
20
|
+
|
|
21
|
+
if (url === undefined) throw new Error(`${URL_VARIABLE} is not set`)
|
|
22
|
+
|
|
23
|
+
return url
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function deployment (instances: context.Dependency[], annotation: Annotation): Dependency {
|
|
28
|
+
const pointer = uris.construct(annotation)
|
|
29
|
+
const variables: Variables = {}
|
|
30
|
+
|
|
31
|
+
for (const instance of instances) {
|
|
32
|
+
const resolution = uris.resolve(instance.locator, pointer)
|
|
33
|
+
const variable: Variable = { name: URL_VARIABLE, value: resolution.url.href }
|
|
34
|
+
|
|
35
|
+
variables[instance.locator.label] = [variable]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { variables }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const URL_VARIABLE = 'TOA_STASH'
|
|
42
|
+
|
|
43
|
+
type Dependency = operations.deployment.Dependency
|
|
44
|
+
type Variables = operations.deployment.Variables
|
|
45
|
+
type Variable = operations.deployment.Variable
|
|
46
|
+
type Annotation = uris.URIs | string
|
package/source/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './extension'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Connector, type extensions } from '@toa.io/core';
|
|
2
|
+
import type { Connection } from './Connection';
|
|
3
|
+
export declare class Aspect extends Connector implements extensions.Aspect {
|
|
4
|
+
readonly name = "stash";
|
|
5
|
+
private readonly redis;
|
|
6
|
+
private readonly redlock;
|
|
7
|
+
constructor(connection: Connection);
|
|
8
|
+
invoke(method: 'store', key: string, value: object): Promise<void>;
|
|
9
|
+
invoke(method: 'fetch', key: string): Promise<object>;
|
|
10
|
+
invoke(method: 'lock', key: string | string[], routine: Routine): Promise<void>;
|
|
11
|
+
invoke(method: string, ...args: unknown[]): Promise<any>;
|
|
12
|
+
private store;
|
|
13
|
+
private fetch;
|
|
14
|
+
private lock;
|
|
15
|
+
}
|
|
16
|
+
type Routine = () => Promise<void>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Aspect.fixtures.js","sourceRoot":"","sources":["../source/Aspect.fixtures.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IACpC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;CACtC,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Aspect = void 0;
|
|
7
|
+
const redlock_1 = __importDefault(require("redlock"));
|
|
8
|
+
const msgpackr_1 = require("msgpackr");
|
|
9
|
+
const core_1 = require("@toa.io/core");
|
|
10
|
+
class Aspect extends core_1.Connector {
|
|
11
|
+
name = 'stash';
|
|
12
|
+
redis;
|
|
13
|
+
redlock;
|
|
14
|
+
constructor(connection) {
|
|
15
|
+
super();
|
|
16
|
+
this.redis = connection.redis;
|
|
17
|
+
this.redlock = new redlock_1.default([this.redis]);
|
|
18
|
+
this.depends(connection);
|
|
19
|
+
}
|
|
20
|
+
async invoke(method, ...args) {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
22
|
+
// @ts-expect-error
|
|
23
|
+
if (typeof this.redis[method] === 'function')
|
|
24
|
+
return this.redis[method](...args);
|
|
25
|
+
if (method === 'store')
|
|
26
|
+
await this.store(args[0], args[1], ...args.slice(2));
|
|
27
|
+
if (method === 'fetch')
|
|
28
|
+
return await this.fetch(args[0]);
|
|
29
|
+
if (method === 'lock')
|
|
30
|
+
await this.lock(args[0], args[1]);
|
|
31
|
+
}
|
|
32
|
+
async store(key, value, ...args) {
|
|
33
|
+
const buffer = (0, msgpackr_1.encode)(value);
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
35
|
+
// @ts-expect-error
|
|
36
|
+
await this.redis.set(key, buffer, ...args);
|
|
37
|
+
}
|
|
38
|
+
async fetch(key) {
|
|
39
|
+
const buffer = await this.redis.getBuffer(key);
|
|
40
|
+
return buffer === null ? null : (0, msgpackr_1.decode)(buffer);
|
|
41
|
+
}
|
|
42
|
+
async lock(key, routine) {
|
|
43
|
+
if (typeof key === 'string')
|
|
44
|
+
key = [key];
|
|
45
|
+
await this.redlock.using(key, 5000, routine);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.Aspect = Aspect;
|
|
49
|
+
//# sourceMappingURL=Aspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA6B;AAC7B,uCAAyC;AACzC,uCAAyD;AAIzD,MAAa,MAAO,SAAQ,gBAAS;IACnB,IAAI,GAAG,OAAO,CAAA;IACb,KAAK,CAAO;IACZ,OAAO,CAAS;IAEjC,YAAoB,UAAsB;QACxC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QAExC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC;IAMM,KAAK,CAAC,MAAM,CAAE,MAAc,EAAE,GAAG,IAAe;QACrD,6DAA6D;QAC7D,mBAAmB;QACnB,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;QAEhF,IAAI,MAAM,KAAK,OAAO;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,IAAI,CAAC,CAAC,CAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAEhG,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAW,CAAC,CAAA;QAElE,IAAI,MAAM,KAAK,MAAM;YAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,IAAI,CAAC,CAAC,CAAwB,CAAC,CAAA;IAC3F,CAAC;IAEO,KAAK,CAAC,KAAK,CAAE,GAAW,EAAE,KAAa,EAAE,GAAG,IAAe;QACjE,MAAM,MAAM,GAAG,IAAA,iBAAM,EAAC,KAAK,CAAC,CAAA;QAE5B,6DAA6D;QAC7D,mBAAmB;QACnB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;IAC5C,CAAC;IAEO,KAAK,CAAC,KAAK,CAAE,GAAW;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QAE9C,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAA,iBAAM,EAAC,MAAM,CAAC,CAAA;IAChD,CAAC;IAEO,KAAK,CAAC,IAAI,CAAE,GAAsB,EAAE,OAAgB;QAC1D,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAA;QAExC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;CACF;AAjDD,wBAiDC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const redlock_1 = __importDefault(require("redlock"));
|
|
30
|
+
const msgpackr_1 = require("msgpackr");
|
|
31
|
+
const randomstring_1 = require("randomstring");
|
|
32
|
+
const Aspect_1 = require("./Aspect");
|
|
33
|
+
const mock = __importStar(require("./Connection.fixtures"));
|
|
34
|
+
jest.mock('redlock');
|
|
35
|
+
let aspect;
|
|
36
|
+
let connection;
|
|
37
|
+
let redis;
|
|
38
|
+
let redlock;
|
|
39
|
+
const key = (0, randomstring_1.generate)();
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
connection = new mock.Connection();
|
|
43
|
+
redis = connection.redis;
|
|
44
|
+
aspect = new Aspect_1.Aspect(connection);
|
|
45
|
+
const m = redlock_1.default.mock;
|
|
46
|
+
redlock = redlock_1.default.mock.instances[0];
|
|
47
|
+
});
|
|
48
|
+
it('should depend on Connection', async () => {
|
|
49
|
+
expect(connection.link).toHaveBeenCalledWith(aspect);
|
|
50
|
+
});
|
|
51
|
+
it('should set', async () => {
|
|
52
|
+
const value = (0, randomstring_1.generate)();
|
|
53
|
+
await aspect.invoke('set', key, value);
|
|
54
|
+
expect(redis.set).toHaveBeenCalledWith(key, value);
|
|
55
|
+
});
|
|
56
|
+
it('should get', async () => {
|
|
57
|
+
const value = await aspect.invoke('get', key);
|
|
58
|
+
expect(redis.get).toHaveBeenCalledWith(key);
|
|
59
|
+
expect(value).toStrictEqual(await redis.get.mock.results[0].value);
|
|
60
|
+
});
|
|
61
|
+
describe('buffers', () => {
|
|
62
|
+
it.each([
|
|
63
|
+
['object', { [(0, randomstring_1.generate)()]: (0, randomstring_1.generate)() }],
|
|
64
|
+
['array', [(0, randomstring_1.generate)(), (0, randomstring_1.generate)()]]
|
|
65
|
+
])('should encode %s', async (_, value) => {
|
|
66
|
+
await aspect.invoke('store', key, value);
|
|
67
|
+
const buffer = (0, msgpackr_1.encode)(value);
|
|
68
|
+
expect(redis.set).toHaveBeenCalledWith(key, buffer);
|
|
69
|
+
});
|
|
70
|
+
it('should pass arguments', async () => {
|
|
71
|
+
const value = [1, 2];
|
|
72
|
+
const buffer = (0, msgpackr_1.encode)(value);
|
|
73
|
+
await aspect.invoke('store', key, value, 'EX', 1000);
|
|
74
|
+
expect(redis.set).toHaveBeenCalledWith(key, buffer, 'EX', 1000);
|
|
75
|
+
});
|
|
76
|
+
it.each([
|
|
77
|
+
['object', { [(0, randomstring_1.generate)()]: (0, randomstring_1.generate)() }],
|
|
78
|
+
['array', [(0, randomstring_1.generate)(), (0, randomstring_1.generate)()]]
|
|
79
|
+
])('should decode %s', async (_, value) => {
|
|
80
|
+
const buffer = (0, msgpackr_1.encode)(value);
|
|
81
|
+
redis.getBuffer.mockImplementationOnce(async () => buffer);
|
|
82
|
+
const output = await aspect.invoke('fetch', key);
|
|
83
|
+
expect(redis.getBuffer).toHaveBeenCalledWith(key);
|
|
84
|
+
expect(output).toStrictEqual(value);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
it('should create DLM', async () => {
|
|
88
|
+
expect(redlock_1.default).toHaveBeenCalledWith([redis]);
|
|
89
|
+
});
|
|
90
|
+
it('should acquire lock', async () => {
|
|
91
|
+
const keys = [(0, randomstring_1.generate)(), (0, randomstring_1.generate)()];
|
|
92
|
+
const callback = jest.fn();
|
|
93
|
+
await aspect.invoke('lock', keys, callback);
|
|
94
|
+
expect(redlock.using).toHaveBeenCalledWith(keys, 5000, callback);
|
|
95
|
+
});
|
|
96
|
+
it('should handle non-array key', async () => {
|
|
97
|
+
const key = (0, randomstring_1.generate)();
|
|
98
|
+
const callback = jest.fn();
|
|
99
|
+
await aspect.invoke('lock', key, callback);
|
|
100
|
+
expect(redlock.using).toHaveBeenCalledWith([key], 5000, callback);
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=Aspect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Aspect.test.js","sourceRoot":"","sources":["../source/Aspect.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import type * as redis from 'ioredis';
|
|
3
|
+
export declare const Redis: jest.Mock<typeof redis.Cluster, any>;
|
|
4
|
+
export declare const Connection: jest.Mock<{
|
|
5
|
+
redis: typeof redis.Cluster;
|
|
6
|
+
link: jest.Mock<any, any>;
|
|
7
|
+
}, []>;
|
|
8
|
+
export declare const ioredis: {
|
|
9
|
+
Redis: jest.Mock<typeof redis.Cluster, any>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ioredis = exports.Connection = exports.Redis = void 0;
|
|
4
|
+
const randomstring_1 = require("randomstring");
|
|
5
|
+
exports.Redis = jest.fn(() => ({
|
|
6
|
+
connect: jest.fn(async () => undefined),
|
|
7
|
+
disconnect: jest.fn(),
|
|
8
|
+
get: jest.fn(async () => (0, randomstring_1.generate)()),
|
|
9
|
+
getBuffer: jest.fn(async () => Buffer.from((0, randomstring_1.generate)())),
|
|
10
|
+
set: jest.fn(async () => undefined),
|
|
11
|
+
on: jest.fn()
|
|
12
|
+
}));
|
|
13
|
+
exports.Connection = jest.fn(() => ({
|
|
14
|
+
redis: new exports.Redis(),
|
|
15
|
+
link: jest.fn()
|
|
16
|
+
}));
|
|
17
|
+
exports.ioredis = { Redis: exports.Redis };
|
|
18
|
+
//# sourceMappingURL=Connection.fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Connection.fixtures.js","sourceRoot":"","sources":["../source/Connection.fixtures.ts"],"names":[],"mappings":";;;AAAA,+CAAuC;AAG1B,QAAA,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;IACvC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE;IACrB,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAA,uBAAQ,GAAE,CAAC;IACpC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAA,uBAAQ,GAAE,CAAC,CAAC;IACvD,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;IACnC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;CACd,CAAC,CAA+C,CAAA;AAEpC,QAAA,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,KAAK,EAAE,IAAI,aAAK,EAAE;IAClB,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;CAChB,CAAC,CAAC,CAAA;AAEU,QAAA,OAAO,GAAG,EAAE,KAAK,EAAL,aAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Connection = void 0;
|
|
4
|
+
const ioredis_1 = require("ioredis");
|
|
5
|
+
const core_1 = require("@toa.io/core");
|
|
6
|
+
class Connection extends core_1.Connector {
|
|
7
|
+
redis;
|
|
8
|
+
constructor(url, locator) {
|
|
9
|
+
super();
|
|
10
|
+
const keyPrefix = `${locator.namespace}:${locator.name}:`;
|
|
11
|
+
const options = { keyPrefix, enableReadyCheck: true };
|
|
12
|
+
this.redis = new ioredis_1.Redis(url, options);
|
|
13
|
+
}
|
|
14
|
+
async close() {
|
|
15
|
+
this.redis.disconnect();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.Connection = Connection;
|
|
19
|
+
//# sourceMappingURL=Connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Connection.js","sourceRoot":"","sources":["../source/Connection.ts"],"names":[],"mappings":";;;AAAA,qCAAoD;AACpD,uCAAsD;AAEtD,MAAa,UAAW,SAAQ,gBAAS;IACvB,KAAK,CAAO;IAE5B,YAAoB,GAAW,EAAE,OAAgB;QAC/C,KAAK,EAAE,CAAA;QAEP,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,GAAG,CAAA;QACzD,MAAM,OAAO,GAAmB,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAA;QAErE,IAAI,CAAC,KAAK,GAAG,IAAI,eAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACtC,CAAC;IAEkB,KAAK,CAAC,KAAK;QAC5B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAA;IACzB,CAAC;CACF;AAfD,gCAeC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const randomstring_1 = require("randomstring");
|
|
4
|
+
const Connection_1 = require("./Connection");
|
|
5
|
+
const core_1 = require("@toa.io/core");
|
|
6
|
+
const Connection_fixtures_1 = require("./Connection.fixtures");
|
|
7
|
+
jest.mock('ioredis', () => ({
|
|
8
|
+
Redis: jest.fn((nodes, options) => new Connection_fixtures_1.Redis(nodes, options))
|
|
9
|
+
}));
|
|
10
|
+
let connection;
|
|
11
|
+
let cluster;
|
|
12
|
+
const url = (0, randomstring_1.generate)();
|
|
13
|
+
const locator = {
|
|
14
|
+
namespace: (0, randomstring_1.generate)(),
|
|
15
|
+
name: (0, randomstring_1.generate)()
|
|
16
|
+
};
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
connection = new Connection_1.Connection(url, locator);
|
|
20
|
+
cluster = Connection_fixtures_1.Redis.mock.results[0].value;
|
|
21
|
+
});
|
|
22
|
+
it('should be instance of Connector', async () => {
|
|
23
|
+
expect(connection).toBeInstanceOf(core_1.Connector);
|
|
24
|
+
});
|
|
25
|
+
it('should connect', async () => {
|
|
26
|
+
const keyPrefix = `${locator.namespace}:${locator.name}:`;
|
|
27
|
+
const options = { keyPrefix, enableReadyCheck: true };
|
|
28
|
+
expect(Connection_fixtures_1.Redis).toHaveBeenCalledWith(url, options);
|
|
29
|
+
// await connection.connect()
|
|
30
|
+
//
|
|
31
|
+
// expect(cluster.connect).toHaveBeenCalled()
|
|
32
|
+
});
|
|
33
|
+
it('should disconnect', async () => {
|
|
34
|
+
await connection.disconnect();
|
|
35
|
+
expect(cluster.disconnect).toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
it('should expose cluster', async () => {
|
|
38
|
+
expect(connection.redis).toStrictEqual(cluster);
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=Connection.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Connection.test.js","sourceRoot":"","sources":["../source/Connection.test.ts"],"names":[],"mappings":";;AAAA,+CAAuC;AACvC,6CAAyC;AACzC,uCAAsD;AACtD,+DAA6C;AAG7C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,KAAe,EAAE,OAA6B,EAAE,EAAE,CAAC,IAAI,2BAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;CAC9F,CAAC,CAAC,CAAA;AAEH,IAAI,UAAsB,CAAA;AAC1B,IAAI,OAAyC,CAAA;AAE7C,MAAM,GAAG,GAAG,IAAA,uBAAQ,GAAE,CAAA;AAEtB,MAAM,OAAO,GAAG;IACd,SAAS,EAAE,IAAA,uBAAQ,GAAE;IACrB,IAAI,EAAE,IAAA,uBAAQ,GAAE;CACK,CAAA;AAEvB,UAAU,CAAC,GAAG,EAAE;IACd,IAAI,CAAC,aAAa,EAAE,CAAA;IAEpB,UAAU,GAAG,IAAI,uBAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACzC,OAAO,GAAG,2BAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;IAC/C,MAAM,CAAC,UAAU,CAAC,CAAC,cAAc,CAAC,gBAAS,CAAC,CAAA;AAC9C,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;IAC9B,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,GAAG,CAAA;IACzD,MAAM,OAAO,GAAyB,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAA;IAE3E,MAAM,CAAC,2BAAK,CAAC,CAAC,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAEhD,6BAA6B;IAC7B,EAAE;IACF,6CAA6C;AAC/C,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;IACjC,MAAM,UAAU,CAAC,UAAU,EAAE,CAAA;IAE7B,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,gBAAgB,EAAE,CAAA;AAC/C,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;IACrC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;AACjD,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { uris } from '@toa.io/pointer';
|
|
2
|
+
import type { Locator, extensions } from '@toa.io/core';
|
|
3
|
+
import type { context } from '@toa.io/norm';
|
|
4
|
+
import type * as operations from '@toa.io/operations';
|
|
5
|
+
export declare class Factory implements extensions.Factory {
|
|
6
|
+
aspect(locator: Locator): extensions.Aspect;
|
|
7
|
+
private url;
|
|
8
|
+
}
|
|
9
|
+
export declare function deployment(instances: context.Dependency[], annotation: Annotation): Dependency;
|
|
10
|
+
type Dependency = operations.deployment.Dependency;
|
|
11
|
+
type Annotation = uris.URIs | string;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deployment = exports.Factory = void 0;
|
|
4
|
+
const pointer_1 = require("@toa.io/pointer");
|
|
5
|
+
const Aspect_1 = require("./Aspect");
|
|
6
|
+
const Connection_1 = require("./Connection");
|
|
7
|
+
class Factory {
|
|
8
|
+
aspect(locator) {
|
|
9
|
+
const url = this.url();
|
|
10
|
+
const connection = new Connection_1.Connection(url, locator);
|
|
11
|
+
return new Aspect_1.Aspect(connection);
|
|
12
|
+
}
|
|
13
|
+
url() {
|
|
14
|
+
if (process.env.TOA_DEV === '1')
|
|
15
|
+
return 'redis://localhost';
|
|
16
|
+
const url = process.env[URL_VARIABLE];
|
|
17
|
+
if (url === undefined)
|
|
18
|
+
throw new Error(`${URL_VARIABLE} is not set`);
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.Factory = Factory;
|
|
23
|
+
function deployment(instances, annotation) {
|
|
24
|
+
const pointer = pointer_1.uris.construct(annotation);
|
|
25
|
+
const variables = {};
|
|
26
|
+
for (const instance of instances) {
|
|
27
|
+
const resolution = pointer_1.uris.resolve(instance.locator, pointer);
|
|
28
|
+
const variable = { name: URL_VARIABLE, value: resolution.url.href };
|
|
29
|
+
variables[instance.locator.label] = [variable];
|
|
30
|
+
}
|
|
31
|
+
return { variables };
|
|
32
|
+
}
|
|
33
|
+
exports.deployment = deployment;
|
|
34
|
+
const URL_VARIABLE = 'TOA_STASH';
|
|
35
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../source/extension.ts"],"names":[],"mappings":";;;AAAA,6CAAsC;AACtC,qCAAiC;AACjC,6CAAyC;AAKzC,MAAa,OAAO;IACX,MAAM,CAAE,OAAgB;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAE/C,OAAO,IAAI,eAAM,CAAC,UAAU,CAAC,CAAA;IAC/B,CAAC;IAEO,GAAG;QACT,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,GAAG;YAAE,OAAO,mBAAmB,CAAA;QAE3D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAErC,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,YAAY,aAAa,CAAC,CAAA;QAEpE,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AAjBD,0BAiBC;AAED,SAAgB,UAAU,CAAE,SAA+B,EAAE,UAAsB;IACjF,MAAM,OAAO,GAAG,cAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IAC1C,MAAM,SAAS,GAAc,EAAE,CAAA;IAE/B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE;QAChC,MAAM,UAAU,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC1D,MAAM,QAAQ,GAAa,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;QAE7E,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;KAC/C;IAED,OAAO,EAAE,SAAS,EAAE,CAAA;AACtB,CAAC;AAZD,gCAYC;AAED,MAAM,YAAY,GAAG,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './extension';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./extension"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA2B"}
|