relayx-js 1.0.19 → 1.1.0
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/.claude/settings.local.json +19 -0
- package/LICENSE +9 -0
- package/README.md +121 -183
- package/examples/example_chat.js +95 -9
- package/examples/example_send_data_on_connect.js +6 -3
- package/package.json +6 -3
- package/realtime/kv_storage.js +196 -0
- package/realtime/models/message.js +26 -0
- package/realtime/queue.js +653 -0
- package/realtime/realtime.js +195 -142
- package/realtime/utils.js +114 -0
- package/tests/test_kv.js +679 -0
- package/tests/test_queue.js +568 -0
- package/tests/test.js +0 -685
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Kvm } from "@nats-io/kv";
|
|
2
|
+
import { ErrorLogging, Logging } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export class KVStore{
|
|
5
|
+
|
|
6
|
+
#kvManager = null
|
|
7
|
+
#kvStore = null
|
|
8
|
+
|
|
9
|
+
#namespace = null;
|
|
10
|
+
|
|
11
|
+
#logger = null;
|
|
12
|
+
#errorLogger = null;
|
|
13
|
+
|
|
14
|
+
constructor(data){
|
|
15
|
+
this.#namespace = data.namespace;
|
|
16
|
+
|
|
17
|
+
this.#kvManager = new Kvm(data.jetstream)
|
|
18
|
+
|
|
19
|
+
this.#logger = new Logging(data.debug)
|
|
20
|
+
|
|
21
|
+
this.#errorLogger = new ErrorLogging()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init(){
|
|
25
|
+
this.#validateInput()
|
|
26
|
+
|
|
27
|
+
this.#kvStore = await this.#kvManager.open(this.#namespace)
|
|
28
|
+
|
|
29
|
+
return this.#kvStore != null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async get(key){
|
|
33
|
+
this.#validateKey(key)
|
|
34
|
+
|
|
35
|
+
try{
|
|
36
|
+
const val = await this.#kvStore.get(key);
|
|
37
|
+
|
|
38
|
+
this.#logger.log("Value for", key)
|
|
39
|
+
|
|
40
|
+
var json = null;
|
|
41
|
+
|
|
42
|
+
json = JSON.parse(val.string())
|
|
43
|
+
|
|
44
|
+
return json
|
|
45
|
+
}catch(err){
|
|
46
|
+
this.#errorLogger.logError({
|
|
47
|
+
err: err,
|
|
48
|
+
op: "kv_read"
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async put(key, value){
|
|
56
|
+
this.#validateKey(key)
|
|
57
|
+
this.#validateValue(value)
|
|
58
|
+
|
|
59
|
+
this.#logger.log(`Creating KV pair for ${key}`)
|
|
60
|
+
|
|
61
|
+
try{
|
|
62
|
+
await this.#kvStore.create(key, JSON.stringify(value))
|
|
63
|
+
}catch(err){
|
|
64
|
+
// The assumption here is that the key might already exist
|
|
65
|
+
try{
|
|
66
|
+
await this.#kvStore.put(key, JSON.stringify(value))
|
|
67
|
+
}catch(err2){
|
|
68
|
+
// The key creation failed because we don't have permission
|
|
69
|
+
this.#errorLogger.logError({
|
|
70
|
+
err: err2,
|
|
71
|
+
op: "kv_write"
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async delete(key){
|
|
78
|
+
this.#validateKey(key)
|
|
79
|
+
|
|
80
|
+
try{
|
|
81
|
+
await this.#kvStore.purge(key);
|
|
82
|
+
}catch(err){
|
|
83
|
+
// The key delete failed because we don't have permission
|
|
84
|
+
this.#errorLogger.logError({
|
|
85
|
+
err: err,
|
|
86
|
+
op: "kv_delete"
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async keys(){
|
|
93
|
+
var keys = [];
|
|
94
|
+
|
|
95
|
+
try{
|
|
96
|
+
var qKeys = await this.#kvStore.keys();
|
|
97
|
+
|
|
98
|
+
for await (const key of qKeys) {
|
|
99
|
+
this.#logger.log("Key: ", key);
|
|
100
|
+
keys.push(key);
|
|
101
|
+
}
|
|
102
|
+
}catch(err){
|
|
103
|
+
this.#errorLogger.logError({
|
|
104
|
+
err: err,
|
|
105
|
+
op: "kv_read"
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return keys;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Utility functions
|
|
114
|
+
#validateInput(){
|
|
115
|
+
if(this.#namespace === null || this.#namespace === undefined || this.#namespace == ""){
|
|
116
|
+
throw new Error("$namespace cannot be null / undefined / empty")
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#validateKey(key){
|
|
121
|
+
if(key == null || key == undefined){
|
|
122
|
+
throw new Error("$key cannot be null / undefined")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if(typeof key != "string"){
|
|
126
|
+
throw new Error("$key cannot be a string")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if(key == ""){
|
|
130
|
+
throw new Error("$key cannot be empty")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate key characters: only a-z, A-Z, 0-9, _, -, ., = and / are allowed
|
|
134
|
+
const validKeyPattern = /^[a-zA-Z0-9_\-\.=\/]+$/;
|
|
135
|
+
if(!validKeyPattern.test(key)){
|
|
136
|
+
throw new Error("$key can only contain alphanumeric characters and the following: _ - . = /")
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#validateValue(value){
|
|
141
|
+
var valueValid = (
|
|
142
|
+
value === null ||
|
|
143
|
+
typeof value == "string" ||
|
|
144
|
+
typeof value == "number" ||
|
|
145
|
+
typeof value == "boolean" ||
|
|
146
|
+
Array.isArray(value) ||
|
|
147
|
+
this.#isJSON(value)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if(!valueValid){
|
|
151
|
+
throw new Error(`$value MUST be null, string, number, boolean, array or json! $value is "${typeof value}"`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#isJSON(data){
|
|
156
|
+
try{
|
|
157
|
+
JSON.stringify(data?.toString())
|
|
158
|
+
return true;
|
|
159
|
+
}catch(err){
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Test helper methods - expose private methods/state for testing
|
|
165
|
+
// Only available when process.env.NODE_ENV == "test"
|
|
166
|
+
testGetNamespace(){
|
|
167
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
168
|
+
return this.#namespace;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
testIsKvStoreInitialized(){
|
|
172
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
173
|
+
return this.#kvStore != null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
testValidateKey(){
|
|
177
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
178
|
+
return (key) => this.#validateKey(key);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
testValidateValue(){
|
|
182
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
183
|
+
return (value) => this.#validateValue(value);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
testValidateInput(){
|
|
187
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
188
|
+
return () => this.#validateInput();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
testIsJSON(){
|
|
192
|
+
if(process.env.NODE_ENV !== "test") return undefined;
|
|
193
|
+
return (data) => this.#isJSON(data);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Message {
|
|
3
|
+
id = null
|
|
4
|
+
message = null
|
|
5
|
+
topic = null
|
|
6
|
+
|
|
7
|
+
msg = null
|
|
8
|
+
|
|
9
|
+
constructor(message){
|
|
10
|
+
this.id = message.id;
|
|
11
|
+
|
|
12
|
+
this.message = message.message;
|
|
13
|
+
|
|
14
|
+
this.topic = message.topic;
|
|
15
|
+
|
|
16
|
+
this.msg = message.msg;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ack(){
|
|
20
|
+
this.msg?.ack()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
nack(millis){
|
|
24
|
+
this.msg?.nak(millis)
|
|
25
|
+
}
|
|
26
|
+
}
|