@shoru/kitten 0.0.6 → 0.1.1
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 +7 -2
- package/internal-test/index.js +4 -0
- package/internal-test/kittenwa.config.js +33 -0
- package/internal-test/plugins/exp.js +7 -0
- package/package.json +10 -10
- package/src/auth/init-session.js +18 -5
- package/src/auth/lmdb-auth-state.js +270 -176
- package/src/client/auth-handler.js +97 -0
- package/src/client/client.js +552 -603
- package/src/client/constants.js +41 -0
- package/src/client/errors.js +47 -0
- package/src/client/event-bus.js +69 -0
- package/src/client/index.js +9 -2
- package/src/client/registry.js +63 -0
- package/src/client/socket-proxy.js +57 -0
- package/src/client/sync-manager.js +79 -0
- package/src/config/default.js +39 -39
- package/src/formatter/format-message.js +189 -144
- package/src/index.js +2 -1
- package/src/internals/config.js +15 -3
- package/src/internals/lmdb-manager.js +79 -54
- package/src/plugins/config.js +1 -1
- package/src/plugins/handlers.js +121 -84
- package/src/plugins/loader.js +19 -13
- package/src/plugins/matcher.js +77 -58
- package/src/plugins/registry.js +184 -94
- package/src/plugins/watcher.js +13 -11
- package/src/utils/time-string.js +31 -19
- package/src/utils/type-conversions.js +11 -5
package/src/plugins/registry.js
CHANGED
|
@@ -1,95 +1,185 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { EVENTS } from './config.js';
|
|
3
|
+
|
|
4
|
+
const plugins = new Map();
|
|
5
|
+
|
|
6
|
+
const eventCounts = new Map([...EVENTS].map(e => [e, 0]));
|
|
7
|
+
|
|
8
|
+
const createBucket = () => {
|
|
9
|
+
const allMatch = new Map();
|
|
10
|
+
return {
|
|
11
|
+
auto: new Map(),
|
|
12
|
+
commandMap: new Map(),
|
|
13
|
+
prefixlessMap: new Map(),
|
|
14
|
+
regexList: new Map(),
|
|
15
|
+
allMatch,
|
|
16
|
+
get match() {
|
|
17
|
+
return allMatch;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const buckets = Object.fromEntries(
|
|
23
|
+
[...EVENTS].map(e => [e, createBucket()])
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Plugin Map Operations
|
|
27
|
+
|
|
28
|
+
export function getPlugin(id) {
|
|
29
|
+
return plugins.get(id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setPlugin(id, plugin) {
|
|
33
|
+
plugins.set(id, plugin);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function deletePlugin(id) {
|
|
37
|
+
plugins.delete(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getAllPlugins() {
|
|
41
|
+
return plugins;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getPluginCount() {
|
|
45
|
+
return plugins.size;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Bucket Operations
|
|
49
|
+
|
|
50
|
+
export function getBucket(event) {
|
|
51
|
+
return buckets[event];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getEventCounts() {
|
|
55
|
+
return eventCounts;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function registerToBuckets(id, plugin) {
|
|
59
|
+
const matchers = plugin._meta?.matchers;
|
|
60
|
+
const isMatch = Boolean(matchers);
|
|
61
|
+
|
|
62
|
+
for (const event of plugin._meta.events) {
|
|
63
|
+
const bucket = buckets[event];
|
|
64
|
+
if (!bucket) continue;
|
|
65
|
+
|
|
66
|
+
if (!isMatch) {
|
|
67
|
+
if (!bucket.auto.has(id)) {
|
|
68
|
+
eventCounts.set(event, (eventCounts.get(event) ?? 0) + 1);
|
|
69
|
+
}
|
|
70
|
+
bucket.auto.set(id, plugin);
|
|
71
|
+
} else {
|
|
72
|
+
if (!bucket.allMatch.has(id)) {
|
|
73
|
+
eventCounts.set(event, (eventCounts.get(event) ?? 0) + 1);
|
|
74
|
+
}
|
|
75
|
+
bucket.allMatch.set(id, plugin);
|
|
76
|
+
|
|
77
|
+
// Index exact string commands with case normalization
|
|
78
|
+
if (matchers.strings && matchers.strings.length > 0) {
|
|
79
|
+
for (const rawCmd of matchers.strings) {
|
|
80
|
+
const normCmd = rawCmd.toLowerCase();
|
|
81
|
+
|
|
82
|
+
if (matchers.prefixes && matchers.prefixes.size > 0) {
|
|
83
|
+
for (const rawPrefix of matchers.prefixes) {
|
|
84
|
+
const normPrefix = rawPrefix.toLowerCase();
|
|
85
|
+
const key = `${normPrefix}${normCmd}`;
|
|
86
|
+
let list = bucket.commandMap.get(key);
|
|
87
|
+
if (!list) {
|
|
88
|
+
list = [];
|
|
89
|
+
bucket.commandMap.set(key, list);
|
|
90
|
+
}
|
|
91
|
+
list.push({ id, plugin, match: normCmd, prefix: normPrefix });
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
let list = bucket.prefixlessMap.get(normCmd);
|
|
95
|
+
if (!list) {
|
|
96
|
+
list = [];
|
|
97
|
+
bucket.prefixlessMap.set(normCmd, list);
|
|
98
|
+
}
|
|
99
|
+
list.push({ id, plugin, match: normCmd, prefix: null });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Index regex matchers
|
|
105
|
+
if (matchers.regexes && matchers.regexes.length > 0) {
|
|
106
|
+
bucket.regexList.set(id, { plugin, regexes: matchers.regexes });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function unregisterFromBuckets(id) {
|
|
113
|
+
const plugin = plugins.get(id);
|
|
114
|
+
const events = plugin?._meta?.events ?? [];
|
|
115
|
+
|
|
116
|
+
for (const event of events) {
|
|
117
|
+
const bucket = buckets[event];
|
|
118
|
+
if (!bucket) continue;
|
|
119
|
+
|
|
120
|
+
let removed = false;
|
|
121
|
+
if (bucket.auto.delete(id)) removed = true;
|
|
122
|
+
if (bucket.allMatch.delete(id)) removed = true;
|
|
123
|
+
|
|
124
|
+
// Clean up commandMap
|
|
125
|
+
for (const [key, list] of bucket.commandMap) {
|
|
126
|
+
const filtered = list.filter(item => item.id !== id);
|
|
127
|
+
if (filtered.length === 0) {
|
|
128
|
+
bucket.commandMap.delete(key);
|
|
129
|
+
} else {
|
|
130
|
+
bucket.commandMap.set(key, filtered);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Clean up prefixlessMap
|
|
135
|
+
for (const [key, list] of bucket.prefixlessMap) {
|
|
136
|
+
const filtered = list.filter(item => item.id !== id);
|
|
137
|
+
if (filtered.length === 0) {
|
|
138
|
+
bucket.prefixlessMap.delete(key);
|
|
139
|
+
} else {
|
|
140
|
+
bucket.prefixlessMap.set(key, filtered);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Clean up regexList
|
|
145
|
+
bucket.regexList.delete(id);
|
|
146
|
+
|
|
147
|
+
if (removed) {
|
|
148
|
+
eventCounts.set(event, Math.max(0, (eventCounts.get(event) ?? 1) - 1));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function unloadByFilePath(filePath) {
|
|
154
|
+
const normalizedTarget = path.resolve(filePath);
|
|
155
|
+
const idsToUnload = [];
|
|
156
|
+
|
|
157
|
+
for (const [id, plugin] of plugins) {
|
|
158
|
+
if (plugin._meta?.filePath && path.resolve(plugin._meta.filePath) === normalizedTarget) {
|
|
159
|
+
idsToUnload.push(id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const id of idsToUnload) {
|
|
164
|
+
unregisterFromBuckets(id);
|
|
165
|
+
plugins.delete(id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return idsToUnload.length;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function clear() {
|
|
172
|
+
plugins.clear();
|
|
173
|
+
|
|
174
|
+
for (const bucket of Object.values(buckets)) {
|
|
175
|
+
bucket.auto.clear();
|
|
176
|
+
bucket.allMatch.clear();
|
|
177
|
+
bucket.commandMap.clear();
|
|
178
|
+
bucket.prefixlessMap.clear();
|
|
179
|
+
bucket.regexList.clear();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const event of EVENTS) {
|
|
183
|
+
eventCounts.set(event, 0);
|
|
184
|
+
}
|
|
95
185
|
}
|
package/src/plugins/watcher.js
CHANGED
|
@@ -50,32 +50,34 @@ export function clearTimers() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function scheduleHMR(filePath, type) {
|
|
53
|
-
|
|
53
|
+
const resolved = path.resolve(filePath);
|
|
54
|
+
clearTimeout(debounceTimers.get(resolved));
|
|
54
55
|
|
|
55
56
|
debounceTimers.set(
|
|
56
|
-
|
|
57
|
+
resolved,
|
|
57
58
|
setTimeout(() => {
|
|
58
|
-
debounceTimers.delete(
|
|
59
|
-
executeHMR(
|
|
59
|
+
debounceTimers.delete(resolved);
|
|
60
|
+
executeHMR(resolved, type);
|
|
60
61
|
}, debounceMs)
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
async function executeHMR(filePath, type) {
|
|
65
|
-
const
|
|
66
|
+
const resolved = path.resolve(filePath);
|
|
67
|
+
const rel = path.relative(PLUGIN_DIR, resolved);
|
|
66
68
|
|
|
67
69
|
try {
|
|
68
|
-
await getLock(
|
|
70
|
+
await getLock(resolved).runExclusive(async () => {
|
|
69
71
|
if (type === 'unlink') {
|
|
70
|
-
const count = unloadByFilePath(
|
|
72
|
+
const count = unloadByFilePath(resolved);
|
|
71
73
|
logger.info(`[HMR] Unloaded: ${rel} (${count})`);
|
|
72
74
|
} else {
|
|
73
75
|
// Load new plugins without registering
|
|
74
|
-
const parent = getParentFolder(path.dirname(
|
|
75
|
-
const loaded = await loadFile(
|
|
76
|
+
const parent = getParentFolder(path.dirname(resolved));
|
|
77
|
+
const loaded = await loadFile(resolved, parent, false);
|
|
76
78
|
|
|
77
79
|
// Remove old plugins for this file
|
|
78
|
-
unloadByFilePath(
|
|
80
|
+
unloadByFilePath(resolved);
|
|
79
81
|
|
|
80
82
|
// Register newly loaded plugins
|
|
81
83
|
for (const [id, plugin] of loaded) {
|
|
@@ -93,7 +95,7 @@ async function executeHMR(filePath, type) {
|
|
|
93
95
|
handleError(`[HMR:${rel}] Failed:`, err);
|
|
94
96
|
} finally {
|
|
95
97
|
if (type === 'unlink') {
|
|
96
|
-
deleteLockIfUnused(
|
|
98
|
+
deleteLockIfUnused(resolved);
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
}
|
package/src/utils/time-string.js
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
-
import { getConfig } from '#internals.js';
|
|
2
|
-
|
|
3
|
-
const { timeZone } = await getConfig();
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
1
|
+
import { getConfig } from '#internals.js';
|
|
2
|
+
|
|
3
|
+
const { timeZone: defaultTimeZone } = await getConfig();
|
|
4
|
+
|
|
5
|
+
const formatters = new Map();
|
|
6
|
+
|
|
7
|
+
function getFormatter(tz) {
|
|
8
|
+
let formatter = formatters.get(tz);
|
|
9
|
+
if (!formatter) {
|
|
10
|
+
formatter = new Intl.DateTimeFormat('en-US', {
|
|
11
|
+
year: 'numeric',
|
|
12
|
+
month: 'long',
|
|
13
|
+
day: 'numeric',
|
|
14
|
+
hour: '2-digit',
|
|
15
|
+
minute: '2-digit',
|
|
16
|
+
second: '2-digit',
|
|
17
|
+
hour12: false,
|
|
18
|
+
timeZone: tz,
|
|
19
|
+
});
|
|
20
|
+
formatters.set(tz, formatter);
|
|
21
|
+
}
|
|
22
|
+
return formatter;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const getTimeString = (timestamp, timeZone = defaultTimeZone) => {
|
|
26
|
+
if (!timestamp) return ['', ''];
|
|
27
|
+
const date = new Date(timestamp * 1000);
|
|
28
|
+
const formatter = getFormatter(timeZone);
|
|
29
|
+
const result = formatter.format(date);
|
|
30
|
+
return result.split(' at ');
|
|
31
|
+
};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
export const isString = x => typeof x ===
|
|
2
|
-
|
|
3
|
-
export const toNumber = x => (x && typeof x.toNumber ===
|
|
4
|
-
|
|
5
|
-
export const toBase64 =
|
|
1
|
+
export const isString = (x) => typeof x === 'string';
|
|
2
|
+
|
|
3
|
+
export const toNumber = (x) => (x && typeof x.toNumber === 'function' ? x.toNumber() : x);
|
|
4
|
+
|
|
5
|
+
export const toBase64 = (x) => {
|
|
6
|
+
if (!x) return x;
|
|
7
|
+
if (typeof x === 'string') return x;
|
|
8
|
+
if (Buffer.isBuffer(x)) return x.toString('base64');
|
|
9
|
+
if (x instanceof Uint8Array) return Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('base64');
|
|
10
|
+
return Buffer.from(x).toString('base64');
|
|
11
|
+
};
|