@toa.io/extensions.stash 1.0.0-alpha.29 → 1.0.0-alpha.292
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/CHANGELOG.md +34 -0
- package/package.json +9 -10
- package/readme.md +2 -15
- package/source/Aspect.ts +59 -30
- package/source/Connection.ts +34 -25
- package/source/Stash.ts +12 -0
- package/source/extension.ts +5 -23
- package/source/index.ts +2 -1
- package/transpiled/Aspect.d.ts +6 -11
- package/transpiled/Aspect.js +47 -30
- package/transpiled/Aspect.js.map +1 -1
- package/transpiled/Connection.d.ts +3 -4
- package/transpiled/Connection.js +32 -28
- package/transpiled/Connection.js.map +1 -1
- package/transpiled/Stash.d.ts +11 -0
- package/transpiled/Stash.js +2 -0
- package/transpiled/Stash.js.map +1 -0
- package/transpiled/extension.d.ts +2 -6
- package/transpiled/extension.js +5 -23
- package/transpiled/extension.js.map +1 -1
- package/transpiled/index.d.ts +2 -1
- package/transpiled/index.js +1 -17
- package/transpiled/index.js.map +1 -1
- package/tsconfig.json +2 -1
- package/tsconfig.tsbuildinfo +1 -0
- package/transpiled/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **definitions:** what a package declares is read from one package ([7052341](https://github.com/toa-io/toa/commit/70523411b5d9e2b204c999aa02365173f3bb518d))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @toa.io/extensions.stash
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# [1.0.0-alpha.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
|
|
22
|
+
|
|
23
|
+
* A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
|
|
24
|
+
|
|
25
|
+
### BREAKING CHANGES
|
|
26
|
+
|
|
27
|
+
* a component that read `process.env.TOA_*` reads `context` instead;
|
|
28
|
+
`echo(input)` no longer substitutes from the environment; a bash operation sees no
|
|
29
|
+
`TOA_*`; images no longer set `USER node` — see migrations/289.md.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
|
|
33
|
+
|
|
34
|
+
**Note:** Version bump only for package @toa.io/extensions.stash
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.stash",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.292",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Toa Stash",
|
|
5
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
7
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,17 +18,15 @@
|
|
|
17
18
|
"access": "public"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@toa.io/
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"@toa.io/definitions": "1.0.0-alpha.292",
|
|
22
|
+
"@toa.io/generic": "1.0.0-alpha.292",
|
|
23
|
+
"@toa.io/pointer": "1.0.0-alpha.292",
|
|
24
|
+
"ioredis": "6.0.0",
|
|
25
|
+
"msgpackr": "2.1.0",
|
|
26
|
+
"openspan": "1.0.0-alpha.286"
|
|
24
27
|
},
|
|
25
28
|
"scripts": {
|
|
26
29
|
"transpile": "tsc"
|
|
27
30
|
},
|
|
28
|
-
"
|
|
29
|
-
"preset": "ts-jest",
|
|
30
|
-
"testEnvironment": "node"
|
|
31
|
-
},
|
|
32
|
-
"gitHead": "186d0e17c12a9016b8076edf925963cbb32b9219"
|
|
31
|
+
"gitHead": "0f6aa3ad9dbec86bb632a7975ea61d1dde1d8fc5"
|
|
33
32
|
}
|
package/readme.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# Toa Stash
|
|
2
2
|
|
|
3
|
-
Shared cache
|
|
4
|
-
and [redlock](https://github.com/mike-marcacci/node-redlock).
|
|
3
|
+
Shared cache on top of [ioredis](https://github.com/redis/ioredis).
|
|
5
4
|
|
|
6
5
|
## Shared cache
|
|
7
6
|
|
|
8
7
|
`stash` aspect exposes [`ioredis` methods](https://redis.github.io/ioredis/classes/Cluster.html).
|
|
9
8
|
|
|
10
9
|
```javascript
|
|
11
|
-
async function computation
|
|
10
|
+
async function computation(input, context) {
|
|
12
11
|
await context.stash.set('key', 'value')
|
|
13
12
|
await context.stash.get('key')
|
|
14
13
|
}
|
|
@@ -27,18 +26,6 @@ starting from third.
|
|
|
27
26
|
|
|
28
27
|
Values are encoded using [msgpack](https://msgpack.org).
|
|
29
28
|
|
|
30
|
-
## Distributed lock manager
|
|
31
|
-
|
|
32
|
-
`async lock<T>(id: string | string[], routine: async? () => T): T`
|
|
33
|
-
|
|
34
|
-
Executes `routine` once a lock is successfully 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
29
|
## Manifest
|
|
43
30
|
|
|
44
31
|
To enable extension for a component, add `null` definition to its manifest:
|
package/source/Aspect.ts
CHANGED
|
@@ -1,47 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
2
2
|
import { encode, decode } from 'msgpackr'
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import type {
|
|
3
|
+
import { console, type SpanOptions } from 'openspan'
|
|
4
|
+
import { Connector } from '@toa.io/core'
|
|
5
|
+
import type { extensions } from '@toa.io/core/types'
|
|
6
|
+
import type { Connection } from './Connection.js'
|
|
7
|
+
import type { Redis, ChainableCommander } from 'ioredis'
|
|
6
8
|
|
|
7
9
|
export class Aspect extends Connector implements extensions.Aspect {
|
|
8
10
|
public readonly name = 'stash'
|
|
9
11
|
private readonly connection: Connection
|
|
10
12
|
private redis: Redis | null = null
|
|
11
|
-
private redlock: Redlock | null = null
|
|
12
13
|
|
|
13
|
-
public constructor
|
|
14
|
+
public constructor(connection: Connection) {
|
|
14
15
|
super()
|
|
15
16
|
|
|
16
17
|
this.connection = connection
|
|
17
18
|
this.depends(connection)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
public
|
|
21
|
-
public
|
|
22
|
-
public
|
|
23
|
-
|
|
24
|
-
public
|
|
21
|
+
public invoke(method: 'store', key: string, value: object): any
|
|
22
|
+
public invoke(method: 'fetch', key: string): any
|
|
23
|
+
public invoke(method: string, ...args: unknown[]): any
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
25
|
+
public invoke(method: string, ...args: unknown[]): any {
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
26
27
|
// @ts-expect-error
|
|
27
|
-
if (typeof this.redis[method] === 'function')
|
|
28
|
+
if (typeof this.redis[method] === 'function') {
|
|
29
|
+
// multi/pipeline return a sync chainable; the span wraps exec() instead
|
|
30
|
+
if (method === 'multi' || method === 'pipeline') {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
32
|
+
// @ts-expect-error
|
|
33
|
+
const chain: ChainableCommander = this.redis[method](...args)
|
|
34
|
+
const exec = chain.exec.bind(chain)
|
|
35
|
+
|
|
36
|
+
chain.exec = async () => {
|
|
37
|
+
const options = span(method, args[0])
|
|
38
|
+
|
|
39
|
+
Object.assign(options.attributes!, { 'db.operation.batch.size': chain.length })
|
|
40
|
+
|
|
41
|
+
return await console.span(options, exec)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return chain
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
48
|
+
// @ts-expect-error
|
|
49
|
+
return console.span(span(method, args[0]), () => this.redis[method](...args))
|
|
50
|
+
}
|
|
28
51
|
|
|
29
52
|
if (method === 'store')
|
|
30
|
-
|
|
53
|
+
console.span(span(method, args[0]), async () => {
|
|
54
|
+
await this.store(args[0] as string, args[1] as object, ...args.slice(2))
|
|
55
|
+
})
|
|
31
56
|
|
|
32
57
|
if (method === 'fetch')
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
return console.span(
|
|
59
|
+
span(method, args[0]),
|
|
60
|
+
async () => await this.fetch(args[0] as string)
|
|
61
|
+
)
|
|
37
62
|
}
|
|
38
63
|
|
|
39
|
-
protected override async open
|
|
40
|
-
this.redis = this.connection.
|
|
41
|
-
this.redlock = new Redlock(this.connection.redises, { retryCount: -1 })
|
|
64
|
+
protected override async open(): Promise<void> {
|
|
65
|
+
this.redis = this.connection.redis
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
private async store
|
|
68
|
+
private async store(key: string, value: object, ...args: unknown[]): Promise<void> {
|
|
45
69
|
const buffer = encode(value)
|
|
46
70
|
|
|
47
71
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -49,22 +73,27 @@ export class Aspect extends Connector implements extensions.Aspect {
|
|
|
49
73
|
await this.redis.set(key, buffer, ...args)
|
|
50
74
|
}
|
|
51
75
|
|
|
52
|
-
private async fetch
|
|
76
|
+
private async fetch(key: string): Promise<object | null> {
|
|
53
77
|
if (this.redis === null) return null
|
|
54
78
|
|
|
55
79
|
const buffer = await this.redis.getBuffer(key)
|
|
56
80
|
|
|
57
81
|
return buffer === null ? null : decode(buffer)
|
|
58
82
|
}
|
|
83
|
+
}
|
|
59
84
|
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
function span(method: string, key: unknown): SpanOptions {
|
|
86
|
+
// https://opentelemetry.io/docs/specs/semconv/database/redis/
|
|
87
|
+
// `db.namespace` names the database node on service graphs,
|
|
88
|
+
// which otherwise displays 'unknown'
|
|
89
|
+
const attributes: Record<string, unknown> = {
|
|
90
|
+
'db.system': 'redis',
|
|
91
|
+
'db.namespace': 'stash',
|
|
92
|
+
'db.operation.name': method
|
|
93
|
+
}
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
if (typeof key === 'string') attributes.key = key
|
|
96
|
+
else if (Array.isArray(key)) attributes.key = key.join(' ')
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
}
|
|
98
|
+
return { name: `${method} stash`, kind: 'client', attributes }
|
|
67
99
|
}
|
|
68
|
-
|
|
69
|
-
type Routine<T> = () => Promise<T>
|
|
70
|
-
type Resources = string | string[]
|
package/source/Connection.ts
CHANGED
|
@@ -1,48 +1,57 @@
|
|
|
1
|
-
import { Redis, type
|
|
1
|
+
import { Redis, type RedisOptions } from 'ioredis'
|
|
2
|
+
import { console } from 'openspan'
|
|
2
3
|
import { Connector, type Locator } from '@toa.io/core'
|
|
3
4
|
import { resolve } from '@toa.io/pointer'
|
|
4
|
-
import { ID } from '
|
|
5
|
+
import { ID } from '@toa.io/definitions/extensions.stash'
|
|
6
|
+
import { environment } from '@toa.io/generic'
|
|
5
7
|
|
|
6
8
|
export class Connection extends Connector {
|
|
7
|
-
public
|
|
8
|
-
|
|
9
|
+
public redis: Redis | null = null
|
|
10
|
+
public readonly locator: Locator
|
|
9
11
|
|
|
10
|
-
public constructor
|
|
12
|
+
public constructor(locator: Locator) {
|
|
11
13
|
super()
|
|
12
14
|
|
|
13
15
|
this.locator = locator
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
protected override async open
|
|
18
|
+
protected override async open(): Promise<void> {
|
|
17
19
|
const keyPrefix = `${this.locator.namespace}:${this.locator.name}:`
|
|
18
|
-
const options: ClusterOptions = { keyPrefix, enableReadyCheck: true, lazyConnect: true }
|
|
19
|
-
const urls = await this.resolveURLs()
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const options: RedisOptions = {
|
|
22
|
+
keyPrefix,
|
|
23
|
+
enableReadyCheck: true,
|
|
24
|
+
lazyConnect: true,
|
|
25
|
+
protocol: 3,
|
|
26
|
+
replyMapping: 'resp3'
|
|
27
|
+
}
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
this.redis = new Redis(await this.resolveURL(), options)
|
|
25
30
|
|
|
26
|
-
await
|
|
31
|
+
await this.redis.connect()
|
|
32
|
+
|
|
33
|
+
console.info('Stash connected to redis', { host: this.redis.options.host })
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
protected override async close
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
protected override async close(): Promise<void> {
|
|
37
|
+
this.redis?.disconnect()
|
|
38
|
+
this.redis = null
|
|
32
39
|
|
|
33
|
-
console.
|
|
40
|
+
console.info('Stash shutdown complete')
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
private async
|
|
37
|
-
|
|
43
|
+
private async resolveURL(): Promise<string> {
|
|
44
|
+
// Toa's own development stack is not on the conventional ports: the applications built on
|
|
45
|
+
// Toa are, and they share the machine. See CONTRIBUTING.md.
|
|
46
|
+
if (environment.get('TOA_DEV') === '1') return 'redis://localhost:31040'
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
const urls = resolve(ID, this.locator.id)
|
|
49
|
+
|
|
50
|
+
// several addresses used to be independent masters for the lock manager, which the atom
|
|
51
|
+
// aspect holds now. A cache is one Redis, and the rest have never been read from
|
|
52
|
+
if (urls.length > 1)
|
|
53
|
+
console.warn('Stash takes the first of several addresses', { count: urls.length })
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
if (process.env.TOA_DEV === '1')
|
|
44
|
-
return ['redis://localhost']
|
|
45
|
-
else
|
|
46
|
-
return resolve(ID, this.locator.id)
|
|
55
|
+
return urls[0]
|
|
47
56
|
}
|
|
48
57
|
}
|
package/source/Stash.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RedisCommander } from 'ioredis'
|
|
2
|
+
|
|
3
|
+
/** What `context.stash` is: every Redis command, and what the aspect adds to them. */
|
|
4
|
+
export interface Stash extends RedisCommander {
|
|
5
|
+
store: <T>(key: string, value: T) => Promise<void>
|
|
6
|
+
fetch: <T>(key: string) => Promise<T>
|
|
7
|
+
lock: <T>(key: Resources, routine: Routine<T>) => Promise<T>
|
|
8
|
+
count: (name: string, interval: number, amount?: number) => number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Routine<T> = () => Promise<T>
|
|
12
|
+
type Resources = string | string[]
|
package/source/extension.ts
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type { context } from '@toa.io/norm'
|
|
6
|
-
import type { Dependency } from '@toa.io/operations'
|
|
1
|
+
import { Aspect } from './Aspect.js'
|
|
2
|
+
import { Connection } from './Connection.js'
|
|
3
|
+
import type { Locator } from '@toa.io/core'
|
|
4
|
+
import type { extensions } from '@toa.io/core/types'
|
|
7
5
|
|
|
8
6
|
export class Factory implements extensions.Factory {
|
|
9
|
-
public aspect
|
|
7
|
+
public aspect(locator: Locator): extensions.Aspect {
|
|
10
8
|
const connection = new Connection(locator)
|
|
11
9
|
|
|
12
10
|
return new Aspect(connection)
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
|
-
|
|
16
|
-
export function deployment (instances: context.Dependency[], annotation: URIMap): Dependency {
|
|
17
|
-
const requests: Request[] = instances.map((instance) => createRequest(instance))
|
|
18
|
-
const variables = createVariables(ID, annotation, requests)
|
|
19
|
-
|
|
20
|
-
return { variables }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function createRequest (instance: context.Dependency): Request {
|
|
24
|
-
return {
|
|
25
|
-
group: instance.locator.label,
|
|
26
|
-
selectors: [instance.locator.id]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const ID = 'stash'
|
package/source/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from './extension'
|
|
1
|
+
export * from './extension.js'
|
|
2
|
+
export type { Stash } from './Stash.js'
|
package/transpiled/Aspect.d.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import { Connector
|
|
2
|
-
import type {
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
import type { Connection } from './Connection.js';
|
|
3
4
|
export declare class Aspect extends Connector implements extensions.Aspect {
|
|
4
5
|
readonly name = "stash";
|
|
5
6
|
private readonly connection;
|
|
6
7
|
private redis;
|
|
7
|
-
private redlock;
|
|
8
8
|
constructor(connection: Connection);
|
|
9
|
-
invoke(method: 'store', key: string, value: object):
|
|
10
|
-
invoke(method: 'fetch', key: string):
|
|
11
|
-
invoke
|
|
12
|
-
invoke(method: string, ...args: unknown[]): Promise<any>;
|
|
9
|
+
invoke(method: 'store', key: string, value: object): any;
|
|
10
|
+
invoke(method: 'fetch', key: string): any;
|
|
11
|
+
invoke(method: string, ...args: unknown[]): any;
|
|
13
12
|
protected open(): Promise<void>;
|
|
14
13
|
private store;
|
|
15
14
|
private fetch;
|
|
16
|
-
private lock;
|
|
17
15
|
}
|
|
18
|
-
type Routine<T> = () => Promise<T>;
|
|
19
|
-
type Resources = string | string[];
|
|
20
|
-
export {};
|
package/transpiled/Aspect.js
CHANGED
|
@@ -1,40 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
exports.Aspect = void 0;
|
|
7
|
-
const redlock_temp_fix_1 = __importDefault(require("redlock-temp-fix"));
|
|
8
|
-
const msgpackr_1 = require("msgpackr");
|
|
9
|
-
const core_1 = require("@toa.io/core");
|
|
10
|
-
class Aspect extends core_1.Connector {
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
2
|
+
import { encode, decode } from 'msgpackr';
|
|
3
|
+
import { console } from 'openspan';
|
|
4
|
+
import { Connector } from '@toa.io/core';
|
|
5
|
+
export class Aspect extends Connector {
|
|
11
6
|
name = 'stash';
|
|
12
7
|
connection;
|
|
13
8
|
redis = null;
|
|
14
|
-
redlock = null;
|
|
15
9
|
constructor(connection) {
|
|
16
10
|
super();
|
|
17
11
|
this.connection = connection;
|
|
18
12
|
this.depends(connection);
|
|
19
13
|
}
|
|
20
|
-
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
15
|
+
invoke(method, ...args) {
|
|
21
16
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
22
17
|
// @ts-expect-error
|
|
23
|
-
if (typeof this.redis[method] === 'function')
|
|
24
|
-
return
|
|
18
|
+
if (typeof this.redis[method] === 'function') {
|
|
19
|
+
// multi/pipeline return a sync chainable; the span wraps exec() instead
|
|
20
|
+
if (method === 'multi' || method === 'pipeline') {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
22
|
+
// @ts-expect-error
|
|
23
|
+
const chain = this.redis[method](...args);
|
|
24
|
+
const exec = chain.exec.bind(chain);
|
|
25
|
+
chain.exec = async () => {
|
|
26
|
+
const options = span(method, args[0]);
|
|
27
|
+
Object.assign(options.attributes, { 'db.operation.batch.size': chain.length });
|
|
28
|
+
return await console.span(options, exec);
|
|
29
|
+
};
|
|
30
|
+
return chain;
|
|
31
|
+
}
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
return console.span(span(method, args[0]), () => this.redis[method](...args));
|
|
35
|
+
}
|
|
25
36
|
if (method === 'store')
|
|
26
|
-
|
|
37
|
+
console.span(span(method, args[0]), async () => {
|
|
38
|
+
await this.store(args[0], args[1], ...args.slice(2));
|
|
39
|
+
});
|
|
27
40
|
if (method === 'fetch')
|
|
28
|
-
return await this.fetch(args[0]);
|
|
29
|
-
if (method === 'lock')
|
|
30
|
-
return await this.lock(args[0], args[1]);
|
|
41
|
+
return console.span(span(method, args[0]), async () => await this.fetch(args[0]));
|
|
31
42
|
}
|
|
32
43
|
async open() {
|
|
33
|
-
this.redis = this.connection.
|
|
34
|
-
this.redlock = new redlock_temp_fix_1.default(this.connection.redises, { retryCount: -1 });
|
|
44
|
+
this.redis = this.connection.redis;
|
|
35
45
|
}
|
|
36
46
|
async store(key, value, ...args) {
|
|
37
|
-
const buffer =
|
|
47
|
+
const buffer = encode(value);
|
|
38
48
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
39
49
|
// @ts-expect-error
|
|
40
50
|
await this.redis.set(key, buffer, ...args);
|
|
@@ -43,15 +53,22 @@ class Aspect extends core_1.Connector {
|
|
|
43
53
|
if (this.redis === null)
|
|
44
54
|
return null;
|
|
45
55
|
const buffer = await this.redis.getBuffer(key);
|
|
46
|
-
return buffer === null ? null :
|
|
47
|
-
}
|
|
48
|
-
async lock(key, routine) {
|
|
49
|
-
if (this.redlock === null)
|
|
50
|
-
return null;
|
|
51
|
-
if (typeof key === 'string')
|
|
52
|
-
key = [key];
|
|
53
|
-
return await this.redlock.using(key, 5000, routine);
|
|
56
|
+
return buffer === null ? null : decode(buffer);
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
function span(method, key) {
|
|
60
|
+
// https://opentelemetry.io/docs/specs/semconv/database/redis/
|
|
61
|
+
// `db.namespace` names the database node on service graphs,
|
|
62
|
+
// which otherwise displays 'unknown'
|
|
63
|
+
const attributes = {
|
|
64
|
+
'db.system': 'redis',
|
|
65
|
+
'db.namespace': 'stash',
|
|
66
|
+
'db.operation.name': method
|
|
67
|
+
};
|
|
68
|
+
if (typeof key === 'string')
|
|
69
|
+
attributes.key = key;
|
|
70
|
+
else if (Array.isArray(key))
|
|
71
|
+
attributes.key = key.join(' ');
|
|
72
|
+
return { name: `${method} stash`, kind: 'client', attributes };
|
|
73
|
+
}
|
|
57
74
|
//# sourceMappingURL=Aspect.js.map
|
package/transpiled/Aspect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Redis } from 'ioredis';
|
|
2
2
|
import { Connector, type Locator } from '@toa.io/core';
|
|
3
3
|
export declare class Connection extends Connector {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
redis: Redis | null;
|
|
5
|
+
readonly locator: Locator;
|
|
6
6
|
constructor(locator: Locator);
|
|
7
7
|
protected open(): Promise<void>;
|
|
8
8
|
protected close(): Promise<void>;
|
|
9
|
-
private
|
|
10
|
-
private resolveURLs;
|
|
9
|
+
private resolveURL;
|
|
11
10
|
}
|
package/transpiled/Connection.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
redises = [];
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { console } from 'openspan';
|
|
3
|
+
import { Connector } from '@toa.io/core';
|
|
4
|
+
import { resolve } from '@toa.io/pointer';
|
|
5
|
+
import { ID } from '@toa.io/definitions/extensions.stash';
|
|
6
|
+
import { environment } from '@toa.io/generic';
|
|
7
|
+
export class Connection extends Connector {
|
|
8
|
+
redis = null;
|
|
10
9
|
locator;
|
|
11
10
|
constructor(locator) {
|
|
12
11
|
super();
|
|
@@ -14,28 +13,33 @@ class Connection extends core_1.Connector {
|
|
|
14
13
|
}
|
|
15
14
|
async open() {
|
|
16
15
|
const keyPrefix = `${this.locator.namespace}:${this.locator.name}:`;
|
|
17
|
-
const options = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
const options = {
|
|
17
|
+
keyPrefix,
|
|
18
|
+
enableReadyCheck: true,
|
|
19
|
+
lazyConnect: true,
|
|
20
|
+
protocol: 3,
|
|
21
|
+
replyMapping: 'resp3'
|
|
22
|
+
};
|
|
23
|
+
this.redis = new Redis(await this.resolveURL(), options);
|
|
24
|
+
await this.redis.connect();
|
|
25
|
+
console.info('Stash connected to redis', { host: this.redis.options.host });
|
|
23
26
|
}
|
|
24
27
|
async close() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.
|
|
28
|
+
this.redis?.disconnect();
|
|
29
|
+
this.redis = null;
|
|
30
|
+
console.info('Stash shutdown complete');
|
|
28
31
|
}
|
|
29
|
-
async
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
async resolveURL() {
|
|
33
|
+
// Toa's own development stack is not on the conventional ports: the applications built on
|
|
34
|
+
// Toa are, and they share the machine. See CONTRIBUTING.md.
|
|
35
|
+
if (environment.get('TOA_DEV') === '1')
|
|
36
|
+
return 'redis://localhost:31040';
|
|
37
|
+
const urls = resolve(ID, this.locator.id);
|
|
38
|
+
// several addresses used to be independent masters for the lock manager, which the atom
|
|
39
|
+
// aspect holds now. A cache is one Redis, and the rest have never been read from
|
|
40
|
+
if (urls.length > 1)
|
|
41
|
+
console.warn('Stash takes the first of several addresses', { count: urls.length });
|
|
42
|
+
return urls[0];
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
|
-
exports.Connection = Connection;
|
|
41
45
|
//# sourceMappingURL=Connection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Connection.js","sourceRoot":"","sources":["../source/Connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Connection.js","sourceRoot":"","sources":["../source/Connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAClC,OAAO,EAAE,SAAS,EAAgB,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,sCAAsC,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE7C,MAAM,OAAO,UAAW,SAAQ,SAAS;IAChC,KAAK,GAAiB,IAAI,CAAA;IACjB,OAAO,CAAS;IAEhC,YAAmB,OAAgB;QACjC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAA;QAEnE,MAAM,OAAO,GAAiB;YAC5B,SAAS;YACT,gBAAgB,EAAE,IAAI;YACtB,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,OAAO;SACtB,CAAA;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,CAAA;QAExD,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;QAE1B,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7E,CAAC;IAEkB,KAAK,CAAC,KAAK;QAC5B,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QAEjB,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAA;IACzC,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,0FAA0F;QAC1F,4DAA4D;QAC5D,IAAI,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG;YAAE,OAAO,yBAAyB,CAAA;QAExE,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAEzC,wFAAwF;QACxF,iFAAiF;QACjF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,4CAA4C,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAEpF,OAAO,IAAI,CAAC,CAAC,CAAC,CAAA;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedisCommander } from 'ioredis';
|
|
2
|
+
/** What `context.stash` is: every Redis command, and what the aspect adds to them. */
|
|
3
|
+
export interface Stash extends RedisCommander {
|
|
4
|
+
store: <T>(key: string, value: T) => Promise<void>;
|
|
5
|
+
fetch: <T>(key: string) => Promise<T>;
|
|
6
|
+
lock: <T>(key: Resources, routine: Routine<T>) => Promise<T>;
|
|
7
|
+
count: (name: string, interval: number, amount?: number) => number;
|
|
8
|
+
}
|
|
9
|
+
type Routine<T> = () => Promise<T>;
|
|
10
|
+
type Resources = string | string[];
|
|
11
|
+
export {};
|