@optimizely/ocp-local-env 1.0.0-beta.4
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/README.md +165 -0
- package/dist/package.json +104 -0
- package/dist/public/bundle.da978bb5437cd82e6d37.js +3 -0
- package/dist/public/bundle.da978bb5437cd82e6d37.js.LICENSE.txt +49 -0
- package/dist/public/bundle.da978bb5437cd82e6d37.js.map +1 -0
- package/dist/public/index.html +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +88 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/executor/FunctionExecutor.d.ts +56 -0
- package/dist/src/executor/FunctionExecutor.js +175 -0
- package/dist/src/executor/FunctionExecutor.js.map +1 -0
- package/dist/src/executor/JobExecutor.d.ts +60 -0
- package/dist/src/executor/JobExecutor.js +203 -0
- package/dist/src/executor/JobExecutor.js.map +1 -0
- package/dist/src/executor/LifecycleExecutor.d.ts +45 -0
- package/dist/src/executor/LifecycleExecutor.js +153 -0
- package/dist/src/executor/LifecycleExecutor.js.map +1 -0
- package/dist/src/executor/watcher.d.ts +63 -0
- package/dist/src/executor/watcher.js +213 -0
- package/dist/src/executor/watcher.js.map +1 -0
- package/dist/src/functions/hello.d.ts +4 -0
- package/dist/src/functions/hello.js +8 -0
- package/dist/src/functions/hello.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/jobs/dailyJob.d.ts +4 -0
- package/dist/src/jobs/dailyJob.js +8 -0
- package/dist/src/jobs/dailyJob.js.map +1 -0
- package/dist/src/local_engine/LocalNotifier.d.ts +10 -0
- package/dist/src/local_engine/LocalNotifier.js +26 -0
- package/dist/src/local_engine/LocalNotifier.js.map +1 -0
- package/dist/src/local_engine/local-engine-child-base.d.ts +79 -0
- package/dist/src/local_engine/local-engine-child-base.js +304 -0
- package/dist/src/local_engine/local-engine-child-base.js.map +1 -0
- package/dist/src/local_engine/local-engine-client.d.ts +80 -0
- package/dist/src/local_engine/local-engine-client.js +333 -0
- package/dist/src/local_engine/local-engine-client.js.map +1 -0
- package/dist/src/local_engine/local-engine-types.d.ts +132 -0
- package/dist/src/local_engine/local-engine-types.js +6 -0
- package/dist/src/local_engine/local-engine-types.js.map +1 -0
- package/dist/src/local_engine/local-engine-unified.d.ts +40 -0
- package/dist/src/local_engine/local-engine-unified.js +406 -0
- package/dist/src/local_engine/local-engine-unified.js.map +1 -0
- package/dist/src/local_engine/local-engine-utils.d.ts +70 -0
- package/dist/src/local_engine/local-engine-utils.js +192 -0
- package/dist/src/local_engine/local-engine-utils.js.map +1 -0
- package/dist/src/local_engine/localSDKConfig.d.ts +30 -0
- package/dist/src/local_engine/localSDKConfig.js +392 -0
- package/dist/src/local_engine/localSDKConfig.js.map +1 -0
- package/dist/src/local_engine/storage/LocalConfigStore.d.ts +56 -0
- package/dist/src/local_engine/storage/LocalConfigStore.js +129 -0
- package/dist/src/local_engine/storage/LocalConfigStore.js.map +1 -0
- package/dist/src/local_engine/storage/LocalJobStore.d.ts +110 -0
- package/dist/src/local_engine/storage/LocalJobStore.js +239 -0
- package/dist/src/local_engine/storage/LocalJobStore.js.map +1 -0
- package/dist/src/local_engine/storage/LocalKVStore.d.ts +105 -0
- package/dist/src/local_engine/storage/LocalKVStore.js +1002 -0
- package/dist/src/local_engine/storage/LocalKVStore.js.map +1 -0
- package/dist/src/local_engine/storage/LocalNotificationStore.d.ts +27 -0
- package/dist/src/local_engine/storage/LocalNotificationStore.js +125 -0
- package/dist/src/local_engine/storage/LocalNotificationStore.js.map +1 -0
- package/dist/src/local_engine/storage/LocalSecretsStore.d.ts +114 -0
- package/dist/src/local_engine/storage/LocalSecretsStore.js +319 -0
- package/dist/src/local_engine/storage/LocalSecretsStore.js.map +1 -0
- package/dist/src/local_engine/storage/LocalSettingsStore.d.ts +161 -0
- package/dist/src/local_engine/storage/LocalSettingsStore.js +417 -0
- package/dist/src/local_engine/storage/LocalSettingsStore.js.map +1 -0
- package/dist/src/local_engine/storage/NumberSet.d.ts +21 -0
- package/dist/src/local_engine/storage/NumberSet.js +32 -0
- package/dist/src/local_engine/storage/NumberSet.js.map +1 -0
- package/dist/src/local_engine/storage/StringSet.d.ts +21 -0
- package/dist/src/local_engine/storage/StringSet.js +32 -0
- package/dist/src/local_engine/storage/StringSet.js.map +1 -0
- package/dist/src/local_engine/types.d.ts +52 -0
- package/dist/src/local_engine/types.js +6 -0
- package/dist/src/local_engine/types.js.map +1 -0
- package/dist/src/local_engine/utils.d.ts +31 -0
- package/dist/src/local_engine/utils.js +126 -0
- package/dist/src/local_engine/utils.js.map +1 -0
- package/dist/src/logging/LogManager.d.ts +89 -0
- package/dist/src/logging/LogManager.js +237 -0
- package/dist/src/logging/LogManager.js.map +1 -0
- package/dist/src/server/api/functions.d.ts +7 -0
- package/dist/src/server/api/functions.js +80 -0
- package/dist/src/server/api/functions.js.map +1 -0
- package/dist/src/server/api/jobs.d.ts +8 -0
- package/dist/src/server/api/jobs.js +242 -0
- package/dist/src/server/api/jobs.js.map +1 -0
- package/dist/src/server/api/lifecycle.d.ts +6 -0
- package/dist/src/server/api/lifecycle.js +73 -0
- package/dist/src/server/api/lifecycle.js.map +1 -0
- package/dist/src/server/api/settings.d.ts +6 -0
- package/dist/src/server/api/settings.js +117 -0
- package/dist/src/server/api/settings.js.map +1 -0
- package/dist/src/server/api/stores.d.ts +2 -0
- package/dist/src/server/api/stores.js +341 -0
- package/dist/src/server/api/stores.js.map +1 -0
- package/dist/src/server/api/v1.d.ts +10 -0
- package/dist/src/server/api/v1.js +711 -0
- package/dist/src/server/api/v1.js.map +1 -0
- package/dist/src/server/api.d.ts +8 -0
- package/dist/src/server/api.js +154 -0
- package/dist/src/server/api.js.map +1 -0
- package/dist/src/server/app-discovery.d.ts +5 -0
- package/dist/src/server/app-discovery.js +81 -0
- package/dist/src/server/app-discovery.js.map +1 -0
- package/dist/src/server/config.d.ts +21 -0
- package/dist/src/server/config.js +100 -0
- package/dist/src/server/config.js.map +1 -0
- package/dist/src/server/websocket.d.ts +0 -0
- package/dist/src/server/websocket.js +2 -0
- package/dist/src/server/websocket.js.map +1 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.js +546 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types/app.d.ts +155 -0
- package/dist/src/types/app.js +24 -0
- package/dist/src/types/app.js.map +1 -0
- package/dist/src/types/kvstore.d.ts +320 -0
- package/dist/src/types/kvstore.js +5 -0
- package/dist/src/types/kvstore.js.map +1 -0
- package/dist/src/ui/components/App.d.ts +6 -0
- package/dist/src/ui/components/App.js +255 -0
- package/dist/src/ui/components/App.js.map +1 -0
- package/dist/src/ui/components/FunctionsView.d.ts +6 -0
- package/dist/src/ui/components/FunctionsView.js +217 -0
- package/dist/src/ui/components/FunctionsView.js.map +1 -0
- package/dist/src/ui/components/JobsView.d.ts +6 -0
- package/dist/src/ui/components/JobsView.js +257 -0
- package/dist/src/ui/components/JobsView.js.map +1 -0
- package/dist/src/ui/components/KVStoreViewer.d.ts +11 -0
- package/dist/src/ui/components/KVStoreViewer.js +168 -0
- package/dist/src/ui/components/KVStoreViewer.js.map +1 -0
- package/dist/src/ui/components/NotificationViewer.d.ts +16 -0
- package/dist/src/ui/components/NotificationViewer.js +69 -0
- package/dist/src/ui/components/NotificationViewer.js.map +1 -0
- package/dist/src/ui/components/SecretsStoreViewer.d.ts +11 -0
- package/dist/src/ui/components/SecretsStoreViewer.js +179 -0
- package/dist/src/ui/components/SecretsStoreViewer.js.map +1 -0
- package/dist/src/ui/components/SettingsStoreViewer.d.ts +24 -0
- package/dist/src/ui/components/SettingsStoreViewer.js +132 -0
- package/dist/src/ui/components/SettingsStoreViewer.js.map +1 -0
- package/dist/src/ui/components/StoreViewer.d.ts +16 -0
- package/dist/src/ui/components/StoreViewer.js +86 -0
- package/dist/src/ui/components/StoreViewer.js.map +1 -0
- package/dist/src/ui/components/TabbedConsole.d.ts +15 -0
- package/dist/src/ui/components/TabbedConsole.js +93 -0
- package/dist/src/ui/components/TabbedConsole.js.map +1 -0
- package/dist/src/ui/components/common/DataTree.d.ts +15 -0
- package/dist/src/ui/components/common/DataTree.js +95 -0
- package/dist/src/ui/components/common/DataTree.js.map +1 -0
- package/dist/src/ui/components/common/EyeIcon.d.ts +11 -0
- package/dist/src/ui/components/common/EyeIcon.js +11 -0
- package/dist/src/ui/components/common/EyeIcon.js.map +1 -0
- package/dist/src/ui/index.d.ts +1 -0
- package/dist/src/ui/index.js +20 -0
- package/dist/src/ui/index.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,1002 @@
|
|
|
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.LocalKVStore = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const NumberSet_1 = require("./NumberSet");
|
|
10
|
+
const StringSet_1 = require("./StringSet");
|
|
11
|
+
/**
|
|
12
|
+
* Local implementation of KVStore that persists data to disk
|
|
13
|
+
* Follows the pattern of LocalSettingsStore with singleton per app directory
|
|
14
|
+
*/
|
|
15
|
+
class LocalKVStore {
|
|
16
|
+
constructor(appRootDir, storeFileName = "kvstore.json") {
|
|
17
|
+
this.appRootDir = appRootDir;
|
|
18
|
+
this.storeFileName = storeFileName;
|
|
19
|
+
this.configDirPath = path_1.default.join(appRootDir, ".ocp-local");
|
|
20
|
+
this.kvStorePath = path_1.default.join(this.configDirPath, storeFileName);
|
|
21
|
+
// Initialize store file if it doesn't exist
|
|
22
|
+
this.ensureStoreFileExists();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get or create a singleton instance for the given app directory
|
|
26
|
+
*/
|
|
27
|
+
static getInstance(appRootDir, storeFileName) {
|
|
28
|
+
const normalizedPath = path_1.default.resolve(appRootDir);
|
|
29
|
+
const instanceKey = `${normalizedPath}:${storeFileName || "kvstore.json"}`;
|
|
30
|
+
if (!this.instances.has(instanceKey)) {
|
|
31
|
+
this.instances.set(instanceKey, new LocalKVStore(normalizedPath, storeFileName));
|
|
32
|
+
}
|
|
33
|
+
return this.instances.get(instanceKey);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Clear singleton instance for testing
|
|
37
|
+
*/
|
|
38
|
+
static clearInstance(appRootDir, storeFileName) {
|
|
39
|
+
const normalizedPath = path_1.default.resolve(appRootDir);
|
|
40
|
+
const instanceKey = `${normalizedPath}:${storeFileName || "kvstore.json"}`;
|
|
41
|
+
this.instances.delete(instanceKey);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Clear all singleton instances for testing
|
|
45
|
+
*/
|
|
46
|
+
static clearAllInstances() {
|
|
47
|
+
this.instances.clear();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Ensure store file exists with empty structure
|
|
51
|
+
*/
|
|
52
|
+
ensureStoreFileExists() {
|
|
53
|
+
try {
|
|
54
|
+
// Ensure the .ocp-local directory exists
|
|
55
|
+
if (!fs_1.default.existsSync(this.configDirPath)) {
|
|
56
|
+
fs_1.default.mkdirSync(this.configDirPath, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
// Create empty store file if it doesn't exist
|
|
59
|
+
if (!fs_1.default.existsSync(this.kvStorePath)) {
|
|
60
|
+
this.saveDataToDisk({});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.warn(`Failed to ensure KV store file exists at ${this.kvStorePath}:`, error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load data from disk and clean up expired entries
|
|
69
|
+
*/
|
|
70
|
+
loadDataFromDisk() {
|
|
71
|
+
try {
|
|
72
|
+
if (!fs_1.default.existsSync(this.kvStorePath)) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
const storeData = fs_1.default.readFileSync(this.kvStorePath, "utf-8");
|
|
76
|
+
const data = JSON.parse(storeData);
|
|
77
|
+
// Clean up expired entries
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const cleanedData = {};
|
|
80
|
+
for (const [key, item] of Object.entries(data)) {
|
|
81
|
+
if (!item.expiresAt || item.expiresAt > now) {
|
|
82
|
+
cleanedData[key] = item;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Save cleaned data if any entries were removed
|
|
86
|
+
if (Object.keys(cleanedData).length !== Object.keys(data).length) {
|
|
87
|
+
this.saveDataToDisk(cleanedData);
|
|
88
|
+
}
|
|
89
|
+
return cleanedData;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.warn(`Failed to load KV store from ${this.kvStorePath}:`, error);
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Save data to disk atomically
|
|
98
|
+
*/
|
|
99
|
+
saveDataToDisk(data) {
|
|
100
|
+
try {
|
|
101
|
+
// Ensure the .ocp-local directory exists
|
|
102
|
+
if (!fs_1.default.existsSync(this.configDirPath)) {
|
|
103
|
+
fs_1.default.mkdirSync(this.configDirPath, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
// Serialize data with custom handling for NumberSet and StringSet
|
|
106
|
+
const serializedData = JSON.stringify(data, (key, value) => {
|
|
107
|
+
if (value instanceof NumberSet_1.NumberSet) {
|
|
108
|
+
return { _type: "NumberSet", values: Array.from(value) };
|
|
109
|
+
}
|
|
110
|
+
if (value instanceof StringSet_1.StringSet) {
|
|
111
|
+
return { _type: "StringSet", values: Array.from(value) };
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}, 2);
|
|
115
|
+
// Write to temp file first, then rename for atomicity
|
|
116
|
+
const tempPath = `${this.kvStorePath}.tmp`;
|
|
117
|
+
fs_1.default.writeFileSync(tempPath, serializedData, "utf-8");
|
|
118
|
+
fs_1.default.renameSync(tempPath, this.kvStorePath);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Failed to save KV store to ${this.kvStorePath}:`, error);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Deserialize data with custom handling for NumberSet and StringSet
|
|
127
|
+
*/
|
|
128
|
+
deserializeValue(value) {
|
|
129
|
+
if (value && typeof value === "object" && value._type) {
|
|
130
|
+
switch (value._type) {
|
|
131
|
+
case "NumberSet":
|
|
132
|
+
return new NumberSet_1.NumberSet(value.values);
|
|
133
|
+
case "StringSet":
|
|
134
|
+
return new StringSet_1.StringSet(value.values);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
return value.map((item) => this.deserializeValue(item));
|
|
139
|
+
}
|
|
140
|
+
if (value && typeof value === "object") {
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const [k, v] of Object.entries(value)) {
|
|
143
|
+
result[k] = this.deserializeValue(v);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get stored item with deserialization
|
|
151
|
+
*/
|
|
152
|
+
getStoredItem(key) {
|
|
153
|
+
const data = this.loadDataFromDisk();
|
|
154
|
+
const item = data[key];
|
|
155
|
+
if (!item) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
// Deserialize the data
|
|
159
|
+
const deserializedData = this.deserializeValue(item.data);
|
|
160
|
+
return {
|
|
161
|
+
...item,
|
|
162
|
+
data: deserializedData,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Store item with optional TTL
|
|
167
|
+
*/
|
|
168
|
+
storeItem(key, data, options) {
|
|
169
|
+
const storeData = this.loadDataFromDisk();
|
|
170
|
+
const item = { data };
|
|
171
|
+
if (options?.ttl !== undefined) {
|
|
172
|
+
item.ttl = options.ttl;
|
|
173
|
+
item.expiresAt = Date.now() + options.ttl * 1000;
|
|
174
|
+
}
|
|
175
|
+
storeData[key] = item;
|
|
176
|
+
this.saveDataToDisk(storeData);
|
|
177
|
+
}
|
|
178
|
+
// ===================================================================================================================
|
|
179
|
+
// Basic Operations
|
|
180
|
+
// ===================================================================================================================
|
|
181
|
+
async get(key, fields) {
|
|
182
|
+
console.log(`[LocalKVStore] GET request: key="${key}", fields=${JSON.stringify(fields)}`);
|
|
183
|
+
try {
|
|
184
|
+
const item = this.getStoredItem(key);
|
|
185
|
+
if (!item) {
|
|
186
|
+
console.log(`[LocalKVStore] GET result: key="${key}" -> {}`);
|
|
187
|
+
return {};
|
|
188
|
+
}
|
|
189
|
+
if (fields && fields.length > 0) {
|
|
190
|
+
// Return only requested fields
|
|
191
|
+
const filteredData = {};
|
|
192
|
+
fields.forEach((field) => {
|
|
193
|
+
if (item.data[field] !== undefined) {
|
|
194
|
+
filteredData[field] = item.data[field];
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
console.log(`[LocalKVStore] GET result: key="${key}" -> ${JSON.stringify(filteredData)}`);
|
|
198
|
+
return filteredData;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Return all data
|
|
202
|
+
console.log(`[LocalKVStore] GET result: key="${key}" -> ${JSON.stringify(item.data)}`);
|
|
203
|
+
return item.data;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.warn(`[LocalKVStore] GET failed: key="${key}":`, error);
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async put(key, value, options) {
|
|
212
|
+
console.log(`[LocalKVStore] PUT request: key="${key}", value=${JSON.stringify(value)}`);
|
|
213
|
+
try {
|
|
214
|
+
// Get previous value
|
|
215
|
+
const previousItem = this.getStoredItem(key);
|
|
216
|
+
const previousValue = previousItem ? previousItem.data : {};
|
|
217
|
+
// Store new value
|
|
218
|
+
this.storeItem(key, value, options);
|
|
219
|
+
console.log(`[LocalKVStore] PUT completed: key="${key}"`);
|
|
220
|
+
return previousValue;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.warn(`[LocalKVStore] PUT failed: key="${key}":`, error);
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async patch(key, valueOrUpdater, options) {
|
|
228
|
+
console.log(`[LocalKVStore] PATCH request: key="${key}"`);
|
|
229
|
+
try {
|
|
230
|
+
// Get current value
|
|
231
|
+
const currentItem = this.getStoredItem(key);
|
|
232
|
+
const currentValue = currentItem ? currentItem.data : {};
|
|
233
|
+
let newValue;
|
|
234
|
+
if (typeof valueOrUpdater === "function") {
|
|
235
|
+
// Updater function
|
|
236
|
+
const updater = valueOrUpdater;
|
|
237
|
+
try {
|
|
238
|
+
// Try with options first (KVPatchUpdaterWithOptions)
|
|
239
|
+
const currentOptions = { ttl: currentItem?.ttl };
|
|
240
|
+
newValue = updater(currentValue, currentOptions);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// Fall back to regular PatchUpdater
|
|
244
|
+
newValue = updater(currentValue);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Value object - merge with current
|
|
249
|
+
newValue = { ...currentValue, ...valueOrUpdater };
|
|
250
|
+
}
|
|
251
|
+
// Store updated value
|
|
252
|
+
this.storeItem(key, newValue, options);
|
|
253
|
+
console.log(`[LocalKVStore] PATCH completed: key="${key}"`);
|
|
254
|
+
return currentValue;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.warn(`[LocalKVStore] PATCH failed: key="${key}":`, error);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async delete(key, fields) {
|
|
262
|
+
console.log(`[LocalKVStore] DELETE request: key="${key}", fields=${JSON.stringify(fields)}`);
|
|
263
|
+
try {
|
|
264
|
+
const storeData = this.loadDataFromDisk();
|
|
265
|
+
const item = storeData[key];
|
|
266
|
+
if (!item) {
|
|
267
|
+
console.log(`[LocalKVStore] DELETE result: key="${key}" -> {} (not found)`);
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
const deserializedData = this.deserializeValue(item.data);
|
|
271
|
+
if (!fields || fields.length === 0) {
|
|
272
|
+
// Delete entire object
|
|
273
|
+
delete storeData[key];
|
|
274
|
+
this.saveDataToDisk(storeData);
|
|
275
|
+
console.log(`[LocalKVStore] DELETE completed: deleted entire key="${key}"`);
|
|
276
|
+
return deserializedData;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Delete specific fields
|
|
280
|
+
const deletedFields = {};
|
|
281
|
+
fields.forEach((field) => {
|
|
282
|
+
if (item.data[field] !== undefined) {
|
|
283
|
+
deletedFields[field] = item.data[field];
|
|
284
|
+
delete item.data[field];
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
this.saveDataToDisk(storeData);
|
|
288
|
+
console.log(`[LocalKVStore] DELETE completed: deleted fields ${JSON.stringify(fields)} from key="${key}"`);
|
|
289
|
+
return this.deserializeValue(deletedFields);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
console.warn(`[LocalKVStore] DELETE failed: key="${key}":`, error);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async exists(key) {
|
|
298
|
+
console.log(`[LocalKVStore] EXISTS request: key="${key}"`);
|
|
299
|
+
try {
|
|
300
|
+
const item = this.getStoredItem(key);
|
|
301
|
+
const exists = item !== null;
|
|
302
|
+
console.log(`[LocalKVStore] EXISTS result: key="${key}" -> ${exists}`);
|
|
303
|
+
return exists;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.warn(`[LocalKVStore] EXISTS failed: key="${key}":`, error);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// ===================================================================================================================
|
|
311
|
+
// Numeric Operations
|
|
312
|
+
// ===================================================================================================================
|
|
313
|
+
async increment(key, field, amount = 1, options) {
|
|
314
|
+
console.log(`[LocalKVStore] INCREMENT request: key="${key}", field="${field}", amount=${amount}`);
|
|
315
|
+
return this.patch(key, (previous) => {
|
|
316
|
+
const currentValue = previous[field];
|
|
317
|
+
if (currentValue !== undefined && typeof currentValue !== "number") {
|
|
318
|
+
throw new Error(`Field "${field}" is not a number`);
|
|
319
|
+
}
|
|
320
|
+
const newValue = (currentValue || 0) + amount;
|
|
321
|
+
return {
|
|
322
|
+
...previous,
|
|
323
|
+
[field]: newValue,
|
|
324
|
+
};
|
|
325
|
+
}, options).then(() => {
|
|
326
|
+
// Return the new value
|
|
327
|
+
return this.get(key, [field]).then((result) => result[field]);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async incrementMulti(key, fieldAmounts, options) {
|
|
331
|
+
console.log(`[LocalKVStore] INCREMENT_MULTI request: key="${key}", fieldAmounts=${JSON.stringify(fieldAmounts)}`);
|
|
332
|
+
return this.patch(key, (previous) => {
|
|
333
|
+
const updated = { ...previous };
|
|
334
|
+
for (const [field, amount] of Object.entries(fieldAmounts)) {
|
|
335
|
+
const currentValue = updated[field];
|
|
336
|
+
if (currentValue !== undefined && typeof currentValue !== "number") {
|
|
337
|
+
throw new Error(`Field "${field}" is not a number`);
|
|
338
|
+
}
|
|
339
|
+
updated[field] = (currentValue || 0) + amount;
|
|
340
|
+
}
|
|
341
|
+
return updated;
|
|
342
|
+
}, options).then(() => {
|
|
343
|
+
// Return the new values
|
|
344
|
+
return this.get(key, Object.keys(fieldAmounts)).then((result) => {
|
|
345
|
+
const newValues = {};
|
|
346
|
+
for (const field of Object.keys(fieldAmounts)) {
|
|
347
|
+
newValues[field] = result[field];
|
|
348
|
+
}
|
|
349
|
+
return newValues;
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
// ===================================================================================================================
|
|
354
|
+
// List Operations
|
|
355
|
+
// ===================================================================================================================
|
|
356
|
+
async shift(key, field) {
|
|
357
|
+
console.log(`[LocalKVStore] SHIFT request: key="${key}", field="${field}"`);
|
|
358
|
+
let shiftedElement = undefined;
|
|
359
|
+
await this.patch(key, (previous) => {
|
|
360
|
+
const currentValue = previous[field];
|
|
361
|
+
if (currentValue === undefined) {
|
|
362
|
+
return previous;
|
|
363
|
+
}
|
|
364
|
+
if (!Array.isArray(currentValue)) {
|
|
365
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
366
|
+
}
|
|
367
|
+
if (currentValue.length === 0) {
|
|
368
|
+
return previous;
|
|
369
|
+
}
|
|
370
|
+
// Capture the first element before removing it
|
|
371
|
+
shiftedElement = currentValue[0];
|
|
372
|
+
// Remove first element
|
|
373
|
+
const updatedList = [...currentValue.slice(1)];
|
|
374
|
+
return {
|
|
375
|
+
...previous,
|
|
376
|
+
[field]: updatedList,
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
console.log(`[LocalKVStore] SHIFT result: key="${key}", field="${field}" -> ${JSON.stringify(shiftedElement)}`);
|
|
380
|
+
return shiftedElement;
|
|
381
|
+
}
|
|
382
|
+
async shiftMulti(key, fieldCounts) {
|
|
383
|
+
console.log(`[LocalKVStore] SHIFT_MULTI request: key="${key}", fieldCounts=${JSON.stringify(fieldCounts)}`);
|
|
384
|
+
const shiftedItems = {};
|
|
385
|
+
return this.patch(key, (previous) => {
|
|
386
|
+
const updated = { ...previous };
|
|
387
|
+
for (const [field, count] of Object.entries(fieldCounts)) {
|
|
388
|
+
const currentValue = updated[field];
|
|
389
|
+
if (currentValue === undefined) {
|
|
390
|
+
shiftedItems[field] = [];
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (!Array.isArray(currentValue)) {
|
|
394
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
395
|
+
}
|
|
396
|
+
const actualCount = Math.min(count, currentValue.length);
|
|
397
|
+
shiftedItems[field] = currentValue.slice(0, actualCount);
|
|
398
|
+
updated[field] = currentValue.slice(actualCount);
|
|
399
|
+
}
|
|
400
|
+
return updated;
|
|
401
|
+
}).then(() => shiftedItems);
|
|
402
|
+
}
|
|
403
|
+
async unshift(key, field, value) {
|
|
404
|
+
console.log(`[LocalKVStore] UNSHIFT request: key="${key}", field="${field}", value=${JSON.stringify(value)}`);
|
|
405
|
+
await this.patch(key, (previous) => {
|
|
406
|
+
const currentValue = previous[field];
|
|
407
|
+
if (currentValue === undefined) {
|
|
408
|
+
// Create new list with the value
|
|
409
|
+
return {
|
|
410
|
+
...previous,
|
|
411
|
+
[field]: [value],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (!Array.isArray(currentValue)) {
|
|
415
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
416
|
+
}
|
|
417
|
+
// Prepend to existing list
|
|
418
|
+
return {
|
|
419
|
+
...previous,
|
|
420
|
+
[field]: [value, ...currentValue],
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async unshiftMulti(key, fieldValues) {
|
|
425
|
+
console.log(`[LocalKVStore] UNSHIFT_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
426
|
+
await this.patch(key, (previous) => {
|
|
427
|
+
const updated = { ...previous };
|
|
428
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
429
|
+
const currentValue = updated[field];
|
|
430
|
+
if (currentValue === undefined) {
|
|
431
|
+
// Create new list with the values
|
|
432
|
+
updated[field] = [...values];
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
if (!Array.isArray(currentValue)) {
|
|
436
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
437
|
+
}
|
|
438
|
+
// Prepend to existing list
|
|
439
|
+
updated[field] = [...values, ...currentValue];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return updated;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async peek(key, field) {
|
|
446
|
+
console.log(`[LocalKVStore] PEEK request: key="${key}", field="${field}"`);
|
|
447
|
+
try {
|
|
448
|
+
const item = this.getStoredItem(key);
|
|
449
|
+
if (!item) {
|
|
450
|
+
console.log(`[LocalKVStore] PEEK result: key="${key}" -> undefined (key not found)`);
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
const fieldValue = item.data[field];
|
|
454
|
+
if (fieldValue === undefined) {
|
|
455
|
+
console.log(`[LocalKVStore] PEEK result: key="${key}", field="${field}" -> undefined (field not found)`);
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
if (!Array.isArray(fieldValue)) {
|
|
459
|
+
console.log(`[LocalKVStore] PEEK result: key="${key}", field="${field}" -> undefined (not a list)`);
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
if (fieldValue.length === 0) {
|
|
463
|
+
console.log(`[LocalKVStore] PEEK result: key="${key}", field="${field}" -> undefined (empty list)`);
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
const firstElement = fieldValue[0];
|
|
467
|
+
console.log(`[LocalKVStore] PEEK result: key="${key}", field="${field}" -> ${JSON.stringify(firstElement)}`);
|
|
468
|
+
return firstElement;
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
console.warn(`[LocalKVStore] PEEK failed: key="${key}", field="${field}":`, error);
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async peekMulti(key, fieldCounts) {
|
|
476
|
+
console.log(`[LocalKVStore] PEEK_MULTI request: key="${key}", fieldCounts=${JSON.stringify(fieldCounts)}`);
|
|
477
|
+
const result = {};
|
|
478
|
+
try {
|
|
479
|
+
const item = this.getStoredItem(key);
|
|
480
|
+
if (!item) {
|
|
481
|
+
// Return empty arrays for all fields
|
|
482
|
+
for (const field of Object.keys(fieldCounts)) {
|
|
483
|
+
result[field] = [];
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
for (const [field, count] of Object.entries(fieldCounts)) {
|
|
488
|
+
const fieldValue = item.data[field];
|
|
489
|
+
if (fieldValue === undefined || !Array.isArray(fieldValue)) {
|
|
490
|
+
result[field] = [];
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const actualCount = Math.min(count, fieldValue.length);
|
|
494
|
+
result[field] = fieldValue.slice(0, actualCount);
|
|
495
|
+
}
|
|
496
|
+
console.log(`[LocalKVStore] PEEK_MULTI result: key="${key}" -> ${JSON.stringify(result)}`);
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
console.warn(`[LocalKVStore] PEEK_MULTI failed: key="${key}":`, error);
|
|
501
|
+
// Return empty arrays for all fields on error
|
|
502
|
+
for (const field of Object.keys(fieldCounts)) {
|
|
503
|
+
result[field] = [];
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async append(key, field, value) {
|
|
509
|
+
console.log(`[LocalKVStore] APPEND request: key="${key}", field="${field}", value=${JSON.stringify(value)}`);
|
|
510
|
+
await this.patch(key, (previous) => {
|
|
511
|
+
const currentValue = previous[field];
|
|
512
|
+
if (currentValue === undefined) {
|
|
513
|
+
// Create new list with the value
|
|
514
|
+
return {
|
|
515
|
+
...previous,
|
|
516
|
+
[field]: [value],
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (!Array.isArray(currentValue)) {
|
|
520
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
521
|
+
}
|
|
522
|
+
// Append to existing list
|
|
523
|
+
return {
|
|
524
|
+
...previous,
|
|
525
|
+
[field]: [...currentValue, value],
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
async appendMulti(key, fieldValues) {
|
|
530
|
+
console.log(`[LocalKVStore] APPEND_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
531
|
+
await this.patch(key, (previous) => {
|
|
532
|
+
const updated = { ...previous };
|
|
533
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
534
|
+
const currentValue = updated[field];
|
|
535
|
+
if (currentValue === undefined) {
|
|
536
|
+
// Create new list with the values
|
|
537
|
+
updated[field] = [...values];
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
if (!Array.isArray(currentValue)) {
|
|
541
|
+
throw new Error(`Field "${field}" is not a list`);
|
|
542
|
+
}
|
|
543
|
+
// Append to existing list
|
|
544
|
+
updated[field] = [...currentValue, ...values];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return updated;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// ===================================================================================================================
|
|
551
|
+
// NumberSet Operations
|
|
552
|
+
// ===================================================================================================================
|
|
553
|
+
async addNumber(key, field, value) {
|
|
554
|
+
console.log(`[LocalKVStore] ADD_NUMBER request: key="${key}", field="${field}", value=${value}`);
|
|
555
|
+
let wasAdded = false;
|
|
556
|
+
await this.patch(key, (previous) => {
|
|
557
|
+
const currentValue = previous[field];
|
|
558
|
+
if (currentValue === undefined) {
|
|
559
|
+
// Create new NumberSet with the value
|
|
560
|
+
const newSet = new NumberSet_1.NumberSet([value]);
|
|
561
|
+
wasAdded = true;
|
|
562
|
+
return {
|
|
563
|
+
...previous,
|
|
564
|
+
[field]: newSet,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (!(currentValue instanceof NumberSet_1.NumberSet)) {
|
|
568
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
569
|
+
}
|
|
570
|
+
// Check if value already exists
|
|
571
|
+
wasAdded = !currentValue.has(value);
|
|
572
|
+
if (wasAdded) {
|
|
573
|
+
// Create new NumberSet with added value
|
|
574
|
+
const newSet = new NumberSet_1.NumberSet(currentValue);
|
|
575
|
+
newSet.add(value);
|
|
576
|
+
return {
|
|
577
|
+
...previous,
|
|
578
|
+
[field]: newSet,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
return previous;
|
|
582
|
+
});
|
|
583
|
+
console.log(`[LocalKVStore] ADD_NUMBER result: key="${key}", field="${field}" -> ${wasAdded}`);
|
|
584
|
+
return wasAdded;
|
|
585
|
+
}
|
|
586
|
+
async addNumberMulti(key, fieldValues) {
|
|
587
|
+
console.log(`[LocalKVStore] ADD_NUMBER_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
588
|
+
const addedValues = {};
|
|
589
|
+
await this.patch(key, (previous) => {
|
|
590
|
+
const updated = { ...previous };
|
|
591
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
592
|
+
const currentValue = updated[field];
|
|
593
|
+
const newlyAdded = new NumberSet_1.NumberSet();
|
|
594
|
+
if (currentValue === undefined) {
|
|
595
|
+
// Create new NumberSet with all values
|
|
596
|
+
const newSet = new NumberSet_1.NumberSet(values);
|
|
597
|
+
updated[field] = newSet;
|
|
598
|
+
addedValues[field] = new NumberSet_1.NumberSet(values);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
if (!(currentValue instanceof NumberSet_1.NumberSet)) {
|
|
602
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
603
|
+
}
|
|
604
|
+
// Create new NumberSet and track newly added values
|
|
605
|
+
const newSet = new NumberSet_1.NumberSet(currentValue);
|
|
606
|
+
for (const value of values) {
|
|
607
|
+
if (!newSet.has(value)) {
|
|
608
|
+
newSet.add(value);
|
|
609
|
+
newlyAdded.add(value);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
updated[field] = newSet;
|
|
613
|
+
addedValues[field] = newlyAdded;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return updated;
|
|
617
|
+
});
|
|
618
|
+
console.log(`[LocalKVStore] ADD_NUMBER_MULTI result: key="${key}" -> ${JSON.stringify(addedValues)}`);
|
|
619
|
+
return addedValues;
|
|
620
|
+
}
|
|
621
|
+
async removeNumber(key, field, value) {
|
|
622
|
+
console.log(`[LocalKVStore] REMOVE_NUMBER request: key="${key}", field="${field}", value=${value}`);
|
|
623
|
+
let wasRemoved = false;
|
|
624
|
+
await this.patch(key, (previous) => {
|
|
625
|
+
const currentValue = previous[field];
|
|
626
|
+
if (currentValue === undefined) {
|
|
627
|
+
return previous;
|
|
628
|
+
}
|
|
629
|
+
if (!(currentValue instanceof NumberSet_1.NumberSet)) {
|
|
630
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
631
|
+
}
|
|
632
|
+
// Check if value exists
|
|
633
|
+
wasRemoved = currentValue.has(value);
|
|
634
|
+
if (wasRemoved) {
|
|
635
|
+
// Create new NumberSet with removed value
|
|
636
|
+
const newSet = new NumberSet_1.NumberSet(currentValue);
|
|
637
|
+
newSet.delete(value);
|
|
638
|
+
return {
|
|
639
|
+
...previous,
|
|
640
|
+
[field]: newSet,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
return previous;
|
|
644
|
+
});
|
|
645
|
+
console.log(`[LocalKVStore] REMOVE_NUMBER result: key="${key}", field="${field}" -> ${wasRemoved}`);
|
|
646
|
+
return wasRemoved;
|
|
647
|
+
}
|
|
648
|
+
async removeNumberMulti(key, fieldValues) {
|
|
649
|
+
console.log(`[LocalKVStore] REMOVE_NUMBER_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
650
|
+
const removedValues = {};
|
|
651
|
+
await this.patch(key, (previous) => {
|
|
652
|
+
const updated = { ...previous };
|
|
653
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
654
|
+
const currentValue = updated[field];
|
|
655
|
+
const actuallyRemoved = new NumberSet_1.NumberSet();
|
|
656
|
+
if (currentValue === undefined) {
|
|
657
|
+
removedValues[field] = actuallyRemoved;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (!(currentValue instanceof NumberSet_1.NumberSet)) {
|
|
661
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
662
|
+
}
|
|
663
|
+
// Create new NumberSet and track removed values
|
|
664
|
+
const newSet = new NumberSet_1.NumberSet(currentValue);
|
|
665
|
+
for (const value of values) {
|
|
666
|
+
if (newSet.has(value)) {
|
|
667
|
+
newSet.delete(value);
|
|
668
|
+
actuallyRemoved.add(value);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
updated[field] = newSet;
|
|
672
|
+
removedValues[field] = actuallyRemoved;
|
|
673
|
+
}
|
|
674
|
+
return updated;
|
|
675
|
+
});
|
|
676
|
+
console.log(`[LocalKVStore] REMOVE_NUMBER_MULTI result: key="${key}" -> ${JSON.stringify(removedValues)}`);
|
|
677
|
+
return removedValues;
|
|
678
|
+
}
|
|
679
|
+
async hasNumber(key, field, value) {
|
|
680
|
+
console.log(`[LocalKVStore] HAS_NUMBER request: key="${key}", field="${field}", value=${value}`);
|
|
681
|
+
try {
|
|
682
|
+
const item = this.getStoredItem(key);
|
|
683
|
+
if (!item) {
|
|
684
|
+
console.log(`[LocalKVStore] HAS_NUMBER result: key="${key}" -> false (key not found)`);
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
const fieldValue = item.data[field];
|
|
688
|
+
if (fieldValue === undefined) {
|
|
689
|
+
console.log(`[LocalKVStore] HAS_NUMBER result: key="${key}", field="${field}" -> false (field not found)`);
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
if (!(fieldValue instanceof NumberSet_1.NumberSet)) {
|
|
693
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
694
|
+
}
|
|
695
|
+
const hasValue = fieldValue.has(value);
|
|
696
|
+
console.log(`[LocalKVStore] HAS_NUMBER result: key="${key}", field="${field}" -> ${hasValue}`);
|
|
697
|
+
return hasValue;
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
console.warn(`[LocalKVStore] HAS_NUMBER failed: key="${key}", field="${field}":`, error);
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async hasNumberMulti(key, fieldValues) {
|
|
705
|
+
console.log(`[LocalKVStore] HAS_NUMBER_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
706
|
+
const result = {};
|
|
707
|
+
try {
|
|
708
|
+
const item = this.getStoredItem(key);
|
|
709
|
+
if (!item) {
|
|
710
|
+
// Return empty NumberSets for all fields
|
|
711
|
+
for (const field of Object.keys(fieldValues)) {
|
|
712
|
+
result[field] = new NumberSet_1.NumberSet();
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
717
|
+
const fieldValue = item.data[field];
|
|
718
|
+
const existingValues = new NumberSet_1.NumberSet();
|
|
719
|
+
if (fieldValue === undefined) {
|
|
720
|
+
result[field] = existingValues;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (!(fieldValue instanceof NumberSet_1.NumberSet)) {
|
|
724
|
+
throw new Error(`Field "${field}" is not a NumberSet`);
|
|
725
|
+
}
|
|
726
|
+
// Check which values exist
|
|
727
|
+
for (const value of values) {
|
|
728
|
+
if (fieldValue.has(value)) {
|
|
729
|
+
existingValues.add(value);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
result[field] = existingValues;
|
|
733
|
+
}
|
|
734
|
+
console.log(`[LocalKVStore] HAS_NUMBER_MULTI result: key="${key}" -> ${JSON.stringify(result)}`);
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
console.warn(`[LocalKVStore] HAS_NUMBER_MULTI failed: key="${key}":`, error);
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// ===================================================================================================================
|
|
743
|
+
// StringSet Operations
|
|
744
|
+
// ===================================================================================================================
|
|
745
|
+
async addString(key, field, value) {
|
|
746
|
+
console.log(`[LocalKVStore] ADD_STRING request: key="${key}", field="${field}", value="${value}"`);
|
|
747
|
+
let wasAdded = false;
|
|
748
|
+
await this.patch(key, (previous) => {
|
|
749
|
+
const currentValue = previous[field];
|
|
750
|
+
if (currentValue === undefined) {
|
|
751
|
+
// Create new StringSet with the value
|
|
752
|
+
const newSet = new StringSet_1.StringSet([value]);
|
|
753
|
+
wasAdded = true;
|
|
754
|
+
return {
|
|
755
|
+
...previous,
|
|
756
|
+
[field]: newSet,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (!(currentValue instanceof StringSet_1.StringSet)) {
|
|
760
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
761
|
+
}
|
|
762
|
+
// Check if value already exists
|
|
763
|
+
wasAdded = !currentValue.has(value);
|
|
764
|
+
if (wasAdded) {
|
|
765
|
+
// Create new StringSet with added value
|
|
766
|
+
const newSet = new StringSet_1.StringSet(currentValue);
|
|
767
|
+
newSet.add(value);
|
|
768
|
+
return {
|
|
769
|
+
...previous,
|
|
770
|
+
[field]: newSet,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return previous;
|
|
774
|
+
});
|
|
775
|
+
console.log(`[LocalKVStore] ADD_STRING result: key="${key}", field="${field}" -> ${wasAdded}`);
|
|
776
|
+
return wasAdded;
|
|
777
|
+
}
|
|
778
|
+
async addStringMulti(key, fieldValues) {
|
|
779
|
+
console.log(`[LocalKVStore] ADD_STRING_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
780
|
+
const addedValues = {};
|
|
781
|
+
await this.patch(key, (previous) => {
|
|
782
|
+
const updated = { ...previous };
|
|
783
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
784
|
+
const currentValue = updated[field];
|
|
785
|
+
const newlyAdded = new StringSet_1.StringSet();
|
|
786
|
+
if (currentValue === undefined) {
|
|
787
|
+
// Create new StringSet with all values
|
|
788
|
+
const newSet = new StringSet_1.StringSet(values);
|
|
789
|
+
updated[field] = newSet;
|
|
790
|
+
addedValues[field] = new StringSet_1.StringSet(values);
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
if (!(currentValue instanceof StringSet_1.StringSet)) {
|
|
794
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
795
|
+
}
|
|
796
|
+
// Create new StringSet and track newly added values
|
|
797
|
+
const newSet = new StringSet_1.StringSet(currentValue);
|
|
798
|
+
for (const value of values) {
|
|
799
|
+
if (!newSet.has(value)) {
|
|
800
|
+
newSet.add(value);
|
|
801
|
+
newlyAdded.add(value);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
updated[field] = newSet;
|
|
805
|
+
addedValues[field] = newlyAdded;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return updated;
|
|
809
|
+
});
|
|
810
|
+
console.log(`[LocalKVStore] ADD_STRING_MULTI result: key="${key}" -> ${JSON.stringify(addedValues)}`);
|
|
811
|
+
return addedValues;
|
|
812
|
+
}
|
|
813
|
+
async removeString(key, field, value) {
|
|
814
|
+
console.log(`[LocalKVStore] REMOVE_STRING request: key="${key}", field="${field}", value="${value}"`);
|
|
815
|
+
let wasRemoved = false;
|
|
816
|
+
await this.patch(key, (previous) => {
|
|
817
|
+
const currentValue = previous[field];
|
|
818
|
+
if (currentValue === undefined) {
|
|
819
|
+
return previous;
|
|
820
|
+
}
|
|
821
|
+
if (!(currentValue instanceof StringSet_1.StringSet)) {
|
|
822
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
823
|
+
}
|
|
824
|
+
// Check if value exists
|
|
825
|
+
wasRemoved = currentValue.has(value);
|
|
826
|
+
if (wasRemoved) {
|
|
827
|
+
// Create new StringSet with removed value
|
|
828
|
+
const newSet = new StringSet_1.StringSet(currentValue);
|
|
829
|
+
newSet.delete(value);
|
|
830
|
+
return {
|
|
831
|
+
...previous,
|
|
832
|
+
[field]: newSet,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return previous;
|
|
836
|
+
});
|
|
837
|
+
console.log(`[LocalKVStore] REMOVE_STRING result: key="${key}", field="${field}" -> ${wasRemoved}`);
|
|
838
|
+
return wasRemoved;
|
|
839
|
+
}
|
|
840
|
+
async removeStringMulti(key, fieldValues) {
|
|
841
|
+
console.log(`[LocalKVStore] REMOVE_STRING_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
842
|
+
const removedValues = {};
|
|
843
|
+
await this.patch(key, (previous) => {
|
|
844
|
+
const updated = { ...previous };
|
|
845
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
846
|
+
const currentValue = updated[field];
|
|
847
|
+
const actuallyRemoved = new StringSet_1.StringSet();
|
|
848
|
+
if (currentValue === undefined) {
|
|
849
|
+
removedValues[field] = actuallyRemoved;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!(currentValue instanceof StringSet_1.StringSet)) {
|
|
853
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
854
|
+
}
|
|
855
|
+
// Create new StringSet and track removed values
|
|
856
|
+
const newSet = new StringSet_1.StringSet(currentValue);
|
|
857
|
+
for (const value of values) {
|
|
858
|
+
if (newSet.has(value)) {
|
|
859
|
+
newSet.delete(value);
|
|
860
|
+
actuallyRemoved.add(value);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
updated[field] = newSet;
|
|
864
|
+
removedValues[field] = actuallyRemoved;
|
|
865
|
+
}
|
|
866
|
+
return updated;
|
|
867
|
+
});
|
|
868
|
+
console.log(`[LocalKVStore] REMOVE_STRING_MULTI result: key="${key}" -> ${JSON.stringify(removedValues)}`);
|
|
869
|
+
return removedValues;
|
|
870
|
+
}
|
|
871
|
+
async hasString(key, field, value) {
|
|
872
|
+
console.log(`[LocalKVStore] HAS_STRING request: key="${key}", field="${field}", value="${value}"`);
|
|
873
|
+
try {
|
|
874
|
+
const item = this.getStoredItem(key);
|
|
875
|
+
if (!item) {
|
|
876
|
+
console.log(`[LocalKVStore] HAS_STRING result: key="${key}" -> false (key not found)`);
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
const fieldValue = item.data[field];
|
|
880
|
+
if (fieldValue === undefined) {
|
|
881
|
+
console.log(`[LocalKVStore] HAS_STRING result: key="${key}", field="${field}" -> false (field not found)`);
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
if (!(fieldValue instanceof StringSet_1.StringSet)) {
|
|
885
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
886
|
+
}
|
|
887
|
+
const hasValue = fieldValue.has(value);
|
|
888
|
+
console.log(`[LocalKVStore] HAS_STRING result: key="${key}", field="${field}" -> ${hasValue}`);
|
|
889
|
+
return hasValue;
|
|
890
|
+
}
|
|
891
|
+
catch (error) {
|
|
892
|
+
console.warn(`[LocalKVStore] HAS_STRING failed: key="${key}", field="${field}":`, error);
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async hasStringMulti(key, fieldValues) {
|
|
897
|
+
console.log(`[LocalKVStore] HAS_STRING_MULTI request: key="${key}", fieldValues=${JSON.stringify(fieldValues)}`);
|
|
898
|
+
const result = {};
|
|
899
|
+
try {
|
|
900
|
+
const item = this.getStoredItem(key);
|
|
901
|
+
if (!item) {
|
|
902
|
+
// Return empty StringSets for all fields
|
|
903
|
+
for (const field of Object.keys(fieldValues)) {
|
|
904
|
+
result[field] = new StringSet_1.StringSet();
|
|
905
|
+
}
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
for (const [field, values] of Object.entries(fieldValues)) {
|
|
909
|
+
const fieldValue = item.data[field];
|
|
910
|
+
const existingValues = new StringSet_1.StringSet();
|
|
911
|
+
if (fieldValue === undefined) {
|
|
912
|
+
result[field] = existingValues;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (!(fieldValue instanceof StringSet_1.StringSet)) {
|
|
916
|
+
throw new Error(`Field "${field}" is not a StringSet`);
|
|
917
|
+
}
|
|
918
|
+
// Check which values exist
|
|
919
|
+
for (const value of values) {
|
|
920
|
+
if (fieldValue.has(value)) {
|
|
921
|
+
existingValues.add(value);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
result[field] = existingValues;
|
|
925
|
+
}
|
|
926
|
+
console.log(`[LocalKVStore] HAS_STRING_MULTI result: key="${key}" -> ${JSON.stringify(result)}`);
|
|
927
|
+
return result;
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
console.warn(`[LocalKVStore] HAS_STRING_MULTI failed: key="${key}":`, error);
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// ===================================================================================================================
|
|
935
|
+
// Helper Methods for API
|
|
936
|
+
// ===================================================================================================================
|
|
937
|
+
/**
|
|
938
|
+
* Get all stored data as a simple key-value object for the UI
|
|
939
|
+
*/
|
|
940
|
+
getAllData() {
|
|
941
|
+
try {
|
|
942
|
+
const storeData = this.loadDataFromDisk();
|
|
943
|
+
const result = {};
|
|
944
|
+
for (const [key, item] of Object.entries(storeData)) {
|
|
945
|
+
result[key] = this.deserializeValue(item.data);
|
|
946
|
+
}
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
console.warn("Failed to get all KV data:", error);
|
|
951
|
+
return {};
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Set a simple key-value pair (for UI convenience)
|
|
956
|
+
*/
|
|
957
|
+
async set(key, value) {
|
|
958
|
+
await this.put(key, value);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Get a simple value by key (for UI convenience)
|
|
962
|
+
*/
|
|
963
|
+
async getValue(key) {
|
|
964
|
+
const result = await this.get(key);
|
|
965
|
+
return result.value;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Delete a key entirely (for UI convenience)
|
|
969
|
+
*/
|
|
970
|
+
async deleteKey(key) {
|
|
971
|
+
const storeData = this.loadDataFromDisk();
|
|
972
|
+
if (key in storeData) {
|
|
973
|
+
delete storeData[key];
|
|
974
|
+
this.saveDataToDisk(storeData);
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Get all keys
|
|
981
|
+
*/
|
|
982
|
+
getAllKeys() {
|
|
983
|
+
try {
|
|
984
|
+
const storeData = this.loadDataFromDisk();
|
|
985
|
+
return Object.keys(storeData);
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
console.warn("Failed to get all KV keys:", error);
|
|
989
|
+
return [];
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Get a shared instance for shared KV store (global across installations of the same app)
|
|
994
|
+
*/
|
|
995
|
+
static getSharedInstance(appRootDir) {
|
|
996
|
+
// Use the same app directory but with a different filename for shared storage
|
|
997
|
+
return LocalKVStore.getInstance(appRootDir, "shared-kvstore.json");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
exports.LocalKVStore = LocalKVStore;
|
|
1001
|
+
LocalKVStore.instances = new Map();
|
|
1002
|
+
//# sourceMappingURL=LocalKVStore.js.map
|