@blackglory/cache-js 0.10.2 → 0.10.3
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 +3 -2
- package/src/cache-client.ts +154 -0
- package/src/contract.ts +40 -0
- package/src/index.ts +1 -0
- package/src/utils/rpc-client.browser.ts +34 -0
- package/src/utils/rpc-client.ts +35 -0
package/package.json
CHANGED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createRPCClient } from '@utils/rpc-client.js'
|
|
2
|
+
import { ClientProxy, BatchClient, BatchClientProxy } from 'delight-rpc'
|
|
3
|
+
import { IAPI, INamespaceStats, IItem } from './contract.js'
|
|
4
|
+
import { timeoutSignal, withAbortSignal } from 'extra-abort'
|
|
5
|
+
import { JSONValue } from '@blackglory/prelude'
|
|
6
|
+
export { INamespaceStats, IItem, IItemMetadata } from './contract.js'
|
|
7
|
+
|
|
8
|
+
export interface ICacheClientOptions {
|
|
9
|
+
server: string
|
|
10
|
+
timeout?: number
|
|
11
|
+
retryIntervalForReconnection?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class CacheClient {
|
|
15
|
+
static async create(options: ICacheClientOptions): Promise<CacheClient> {
|
|
16
|
+
const { client, batchClient, proxy, close } = await createRPCClient(
|
|
17
|
+
options.server
|
|
18
|
+
, options.retryIntervalForReconnection
|
|
19
|
+
)
|
|
20
|
+
return new CacheClient(client, batchClient, proxy, close, options.timeout)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private constructor(
|
|
24
|
+
private client: ClientProxy<IAPI>
|
|
25
|
+
, private batchClient: BatchClient
|
|
26
|
+
, private batchProxy: BatchClientProxy<IAPI, unknown>
|
|
27
|
+
, private closeClients: () => Promise<void>
|
|
28
|
+
, private timeout?: number
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async close(): Promise<void> {
|
|
32
|
+
await this.closeClients()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getNamespaceStats(
|
|
36
|
+
namespace: string
|
|
37
|
+
, timeout?: number
|
|
38
|
+
): Promise<INamespaceStats> {
|
|
39
|
+
return await this.withTimeout(
|
|
40
|
+
() => this.client.getNamespaceStats(namespace)
|
|
41
|
+
, timeout ?? this.timeout
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getAllNamespaces(timeout?: number): Promise<string[]> {
|
|
46
|
+
return await this.withTimeout(
|
|
47
|
+
() => this.client.getAllNamespaces()
|
|
48
|
+
, timeout ?? this.timeout
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getAllItemKeys(namespace: string, timeout?: number): Promise<string[]> {
|
|
53
|
+
return await this.withTimeout(
|
|
54
|
+
() => this.client.getAllItemKeys(namespace)
|
|
55
|
+
, timeout ?? this.timeout
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async hasItem(
|
|
60
|
+
namespace: string
|
|
61
|
+
, itemKey: string
|
|
62
|
+
, timeout?: number
|
|
63
|
+
): Promise<boolean> {
|
|
64
|
+
return await this.withTimeout(
|
|
65
|
+
() => this.client.hasItem(namespace, itemKey)
|
|
66
|
+
, timeout ?? this.timeout
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getItem(
|
|
71
|
+
namespace: string
|
|
72
|
+
, itemKey: string
|
|
73
|
+
, timeout?: number
|
|
74
|
+
): Promise<IItem | null> {
|
|
75
|
+
return await this.withTimeout(
|
|
76
|
+
() => this.client.getItem(namespace, itemKey)
|
|
77
|
+
, timeout ?? this.timeout
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getItemValue(
|
|
82
|
+
namespace: string
|
|
83
|
+
, itemKey: string
|
|
84
|
+
, timeout?: number
|
|
85
|
+
): Promise<JSONValue | null> {
|
|
86
|
+
return await this.withTimeout(
|
|
87
|
+
() => this.client.getItemValue(namespace, itemKey)
|
|
88
|
+
, timeout ?? this.timeout
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getItemValues(
|
|
93
|
+
namespace: string
|
|
94
|
+
, itemKeys: string[]
|
|
95
|
+
, timeout?: number
|
|
96
|
+
): Promise<Array<JSONValue | null>> {
|
|
97
|
+
return await this.withTimeout(
|
|
98
|
+
async () => {
|
|
99
|
+
const results = await this.batchClient.parallel(
|
|
100
|
+
...itemKeys.map(key => this.batchProxy.getItemValue(namespace, key))
|
|
101
|
+
)
|
|
102
|
+
return results.map(result => result.unwrap())
|
|
103
|
+
}
|
|
104
|
+
, timeout ?? this.timeout
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async setItem(
|
|
109
|
+
namespace: string
|
|
110
|
+
, itemKey: string
|
|
111
|
+
, itemValue: JSONValue
|
|
112
|
+
, timeToLive: number | null
|
|
113
|
+
, timeout?: number
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
await this.withTimeout(
|
|
116
|
+
() => this.client.setItem(
|
|
117
|
+
namespace
|
|
118
|
+
, itemKey
|
|
119
|
+
, itemValue
|
|
120
|
+
, timeToLive
|
|
121
|
+
)
|
|
122
|
+
, timeout ?? this.timeout
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async removeItem(
|
|
127
|
+
namespace: string
|
|
128
|
+
, itemKey: string
|
|
129
|
+
, timeout?: number
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
await this.withTimeout(
|
|
132
|
+
() => this.client.removeItem(namespace, itemKey)
|
|
133
|
+
, timeout ?? this.timeout
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async clearItemsByNamespace(namespace: string, timeout?: number): Promise<void> {
|
|
138
|
+
await this.withTimeout(
|
|
139
|
+
() => this.client.clearItemsByNamespace(namespace)
|
|
140
|
+
, timeout ?? this.timeout
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async withTimeout<T>(
|
|
145
|
+
fn: () => PromiseLike<T>
|
|
146
|
+
, timeout: number | undefined = this.timeout
|
|
147
|
+
): Promise<T> {
|
|
148
|
+
if (timeout) {
|
|
149
|
+
return await withAbortSignal(timeoutSignal(timeout), fn)
|
|
150
|
+
} else {
|
|
151
|
+
return await fn()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/contract.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { JSONValue } from '@blackglory/prelude'
|
|
2
|
+
|
|
3
|
+
export const expectedVersion = '^0.9.0'
|
|
4
|
+
|
|
5
|
+
export interface INamespaceStats {
|
|
6
|
+
items: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IItem {
|
|
10
|
+
value: JSONValue
|
|
11
|
+
metadata: IItemMetadata
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IItemMetadata {
|
|
15
|
+
updatedAt: number
|
|
16
|
+
timeToLive: number | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IAPI {
|
|
20
|
+
getAllNamespaces(): string[]
|
|
21
|
+
getAllItemKeys(namespace: string): string[]
|
|
22
|
+
|
|
23
|
+
getNamespaceStats(namespace: string): INamespaceStats
|
|
24
|
+
|
|
25
|
+
hasItem(namespace: string, itemKey: string): boolean
|
|
26
|
+
|
|
27
|
+
getItem(namespace: string, itemKey: string): IItem | null
|
|
28
|
+
getItemValue(namespace: string, itemKey: string): JSONValue | null
|
|
29
|
+
|
|
30
|
+
setItem(
|
|
31
|
+
namespace: string
|
|
32
|
+
, itemKey: string
|
|
33
|
+
, itemValue: JSONValue
|
|
34
|
+
, timeToLive: number | null /* ms */
|
|
35
|
+
): null
|
|
36
|
+
|
|
37
|
+
removeItem(namespace: string, itemKey: string): null
|
|
38
|
+
|
|
39
|
+
clearItemsByNamespace(namespace: string): null
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cache-client.js'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IAPI, expectedVersion } from '@src/contract.js'
|
|
2
|
+
import { ClientProxy, BatchClient, BatchClientProxy, createBatchProxy } from 'delight-rpc'
|
|
3
|
+
import { createClient, createBatchClient } from '@delight-rpc/extra-native-websocket'
|
|
4
|
+
import { ExtraNativeWebSocket, autoReconnect } from 'extra-native-websocket'
|
|
5
|
+
|
|
6
|
+
export async function createRPCClient(
|
|
7
|
+
url: string
|
|
8
|
+
, retryIntervalForReconnection?: number
|
|
9
|
+
): Promise<{
|
|
10
|
+
client: ClientProxy<IAPI>
|
|
11
|
+
batchClient: BatchClient<IAPI>
|
|
12
|
+
proxy: BatchClientProxy<IAPI, unknown>
|
|
13
|
+
close: () => Promise<void>
|
|
14
|
+
}> {
|
|
15
|
+
const ws = new ExtraNativeWebSocket(() => new WebSocket(url))
|
|
16
|
+
const cancelAutoReconnect = autoReconnect(ws, retryIntervalForReconnection)
|
|
17
|
+
await ws.connect()
|
|
18
|
+
|
|
19
|
+
const [client, closeClient] = createClient<IAPI>(ws, { expectedVersion })
|
|
20
|
+
const [batchClient, closeBatchClient] = createBatchClient(ws, { expectedVersion })
|
|
21
|
+
const proxy = createBatchProxy<IAPI>()
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
client
|
|
25
|
+
, batchClient
|
|
26
|
+
, proxy
|
|
27
|
+
, close: async () => {
|
|
28
|
+
closeClient()
|
|
29
|
+
closeBatchClient()
|
|
30
|
+
cancelAutoReconnect()
|
|
31
|
+
await ws.close()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IAPI, expectedVersion } from '@src/contract.js'
|
|
2
|
+
import { ClientProxy, BatchClient, BatchClientProxy, createBatchProxy } from 'delight-rpc'
|
|
3
|
+
import { createClient, createBatchClient } from '@delight-rpc/extra-websocket'
|
|
4
|
+
import { WebSocket } from 'ws'
|
|
5
|
+
import { ExtraWebSocket, autoReconnect } from 'extra-websocket'
|
|
6
|
+
|
|
7
|
+
export async function createRPCClient(
|
|
8
|
+
url: string
|
|
9
|
+
, retryIntervalForReconnection?: number
|
|
10
|
+
): Promise<{
|
|
11
|
+
client: ClientProxy<IAPI>
|
|
12
|
+
batchClient: BatchClient<IAPI>
|
|
13
|
+
proxy: BatchClientProxy<IAPI, unknown>
|
|
14
|
+
close: () => Promise<void>
|
|
15
|
+
}> {
|
|
16
|
+
const ws = new ExtraWebSocket(() => new WebSocket(url))
|
|
17
|
+
const cancelAutoReconnect = autoReconnect(ws, retryIntervalForReconnection)
|
|
18
|
+
await ws.connect()
|
|
19
|
+
|
|
20
|
+
const [client, closeClient] = createClient<IAPI>(ws, { expectedVersion })
|
|
21
|
+
const [batchClient, closeBatchClient] = createBatchClient(ws, { expectedVersion })
|
|
22
|
+
const proxy = createBatchProxy<IAPI>()
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
client
|
|
26
|
+
, batchClient
|
|
27
|
+
, proxy
|
|
28
|
+
, close: async () => {
|
|
29
|
+
closeClient()
|
|
30
|
+
closeBatchClient()
|
|
31
|
+
cancelAutoReconnect()
|
|
32
|
+
await ws.close()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|