mumpix 1.0.18 → 1.0.19
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 +12 -0
- package/README.md +64 -0
- package/bin/mumpix.js +0 -0
- package/examples/temporal-memory.js +80 -0
- package/package.json +7 -3
- package/src/core/MumpixDB.js +158 -10
- package/src/core/license.js +16 -21
- package/src/core/store.js +109 -24
- package/src/index.js +8 -0
- package/src/temporal/engine.js +1894 -0
- package/src/temporal/indexes.js +178 -0
- package/src/temporal/operators.js +186 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { dedupeEvents, normalizeText } = require('./operators');
|
|
4
|
+
|
|
5
|
+
function addToMapSet(map, key, value) {
|
|
6
|
+
if (!key) return;
|
|
7
|
+
if (!map.has(key)) map.set(key, []);
|
|
8
|
+
map.get(key).push(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function uniqueWords(value) {
|
|
12
|
+
return Array.from(new Set(
|
|
13
|
+
normalizeText(value)
|
|
14
|
+
.split(' ')
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.filter((token) => token.length > 2)
|
|
17
|
+
));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildEventIndex(events) {
|
|
21
|
+
const normalized = dedupeEvents(events);
|
|
22
|
+
const keys = new Set();
|
|
23
|
+
const byId = new Map();
|
|
24
|
+
const byTitle = new Map();
|
|
25
|
+
const byAlias = new Map();
|
|
26
|
+
const byEntity = new Map();
|
|
27
|
+
const byType = new Map();
|
|
28
|
+
const byKind = new Map();
|
|
29
|
+
const byDate = new Map();
|
|
30
|
+
const byWord = new Map();
|
|
31
|
+
const byWorkspace = new Map();
|
|
32
|
+
const byRepo = new Map();
|
|
33
|
+
const bySource = new Map();
|
|
34
|
+
|
|
35
|
+
for (const event of normalized) {
|
|
36
|
+
keys.add(signatureForEvent(event));
|
|
37
|
+
if (event.event_id) byId.set(event.event_id, event);
|
|
38
|
+
|
|
39
|
+
addToMapSet(byTitle, normalizeText(event.title), event);
|
|
40
|
+
for (const alias of event.aliases || []) addToMapSet(byAlias, normalizeText(alias), event);
|
|
41
|
+
for (const entity of event.entities || []) addToMapSet(byEntity, normalizeText(entity), event);
|
|
42
|
+
addToMapSet(byType, normalizeText(event.event_type), event);
|
|
43
|
+
addToMapSet(byKind, normalizeText(event.event_kind), event);
|
|
44
|
+
if (event.event_date) addToMapSet(byDate, event.event_date, event);
|
|
45
|
+
if (event.workspace) addToMapSet(byWorkspace, normalizeText(event.workspace), event);
|
|
46
|
+
if (event.repo) addToMapSet(byRepo, normalizeText(event.repo), event);
|
|
47
|
+
if (event.source) addToMapSet(bySource, normalizeText(event.source), event);
|
|
48
|
+
|
|
49
|
+
for (const token of uniqueWords([
|
|
50
|
+
event.title,
|
|
51
|
+
(event.aliases || []).join(' '),
|
|
52
|
+
(event.entities || []).join(' '),
|
|
53
|
+
event.event_type,
|
|
54
|
+
event.event_kind,
|
|
55
|
+
event.workspace,
|
|
56
|
+
event.repo,
|
|
57
|
+
event.source,
|
|
58
|
+
].join(' '))) {
|
|
59
|
+
addToMapSet(byWord, token, event);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
events: normalized,
|
|
65
|
+
byId,
|
|
66
|
+
byTitle,
|
|
67
|
+
byAlias,
|
|
68
|
+
byEntity,
|
|
69
|
+
byType,
|
|
70
|
+
byKind,
|
|
71
|
+
byDate,
|
|
72
|
+
byWord,
|
|
73
|
+
byWorkspace,
|
|
74
|
+
byRepo,
|
|
75
|
+
bySource,
|
|
76
|
+
_keys: keys,
|
|
77
|
+
stats: {
|
|
78
|
+
events: normalized.length,
|
|
79
|
+
titles: byTitle.size,
|
|
80
|
+
aliases: byAlias.size,
|
|
81
|
+
entities: byEntity.size,
|
|
82
|
+
types: byType.size,
|
|
83
|
+
kinds: byKind.size,
|
|
84
|
+
dates: byDate.size,
|
|
85
|
+
words: byWord.size,
|
|
86
|
+
workspaces: byWorkspace.size,
|
|
87
|
+
repos: byRepo.size,
|
|
88
|
+
sources: bySource.size,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function signatureForEvent(event) {
|
|
94
|
+
return [
|
|
95
|
+
event.event_type,
|
|
96
|
+
event.event_kind,
|
|
97
|
+
normalizeText(event.title),
|
|
98
|
+
event.event_date || '',
|
|
99
|
+
event.mention_date || '',
|
|
100
|
+
event.workspace || '',
|
|
101
|
+
event.repo || '',
|
|
102
|
+
event.source || '',
|
|
103
|
+
event.source_message_key || '',
|
|
104
|
+
].join('|');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function appendEventIndex(index, events) {
|
|
108
|
+
const target = index || buildEventIndex([]);
|
|
109
|
+
const additions = dedupeEvents(events);
|
|
110
|
+
for (const event of additions) {
|
|
111
|
+
const key = signatureForEvent(event);
|
|
112
|
+
if (target._keys.has(key)) continue;
|
|
113
|
+
target._keys.add(key);
|
|
114
|
+
target.events.push(event);
|
|
115
|
+
if (event.event_id) target.byId.set(event.event_id, event);
|
|
116
|
+
addToMapSet(target.byTitle, normalizeText(event.title), event);
|
|
117
|
+
for (const alias of event.aliases || []) addToMapSet(target.byAlias, normalizeText(alias), event);
|
|
118
|
+
for (const entity of event.entities || []) addToMapSet(target.byEntity, normalizeText(entity), event);
|
|
119
|
+
addToMapSet(target.byType, normalizeText(event.event_type), event);
|
|
120
|
+
addToMapSet(target.byKind, normalizeText(event.event_kind), event);
|
|
121
|
+
if (event.event_date) addToMapSet(target.byDate, event.event_date, event);
|
|
122
|
+
if (event.workspace) addToMapSet(target.byWorkspace, normalizeText(event.workspace), event);
|
|
123
|
+
if (event.repo) addToMapSet(target.byRepo, normalizeText(event.repo), event);
|
|
124
|
+
if (event.source) addToMapSet(target.bySource, normalizeText(event.source), event);
|
|
125
|
+
|
|
126
|
+
for (const token of uniqueWords([
|
|
127
|
+
event.title,
|
|
128
|
+
(event.aliases || []).join(' '),
|
|
129
|
+
(event.entities || []).join(' '),
|
|
130
|
+
event.event_type,
|
|
131
|
+
event.event_kind,
|
|
132
|
+
event.workspace,
|
|
133
|
+
event.repo,
|
|
134
|
+
event.source,
|
|
135
|
+
].join(' '))) {
|
|
136
|
+
addToMapSet(target.byWord, token, event);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
target.stats = {
|
|
141
|
+
events: target.events.length,
|
|
142
|
+
titles: target.byTitle.size,
|
|
143
|
+
aliases: target.byAlias.size,
|
|
144
|
+
entities: target.byEntity.size,
|
|
145
|
+
types: target.byType.size,
|
|
146
|
+
kinds: target.byKind.size,
|
|
147
|
+
dates: target.byDate.size,
|
|
148
|
+
words: target.byWord.size,
|
|
149
|
+
workspaces: target.byWorkspace.size,
|
|
150
|
+
repos: target.byRepo.size,
|
|
151
|
+
sources: target.bySource.size,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return target;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function eventCandidatesForQuery(index, query) {
|
|
158
|
+
if (!index) return [];
|
|
159
|
+
const tokens = uniqueWords(query);
|
|
160
|
+
const seen = new Map();
|
|
161
|
+
|
|
162
|
+
for (const token of tokens) {
|
|
163
|
+
const matches = index.byWord.get(token) || [];
|
|
164
|
+
for (const event of matches) {
|
|
165
|
+
const key = event.event_id || `${event.title}|${event.event_date || event.mention_date || ''}`;
|
|
166
|
+
if (!seen.has(key)) seen.set(key, event);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return Array.from(seen.values());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
buildEventIndex,
|
|
175
|
+
appendEventIndex,
|
|
176
|
+
eventCandidatesForQuery,
|
|
177
|
+
uniqueWords,
|
|
178
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Generated by scripts/build-package.cjs from src/memory/events/operators.js
|
|
4
|
+
// Do not edit this file directly in the publish tree.
|
|
5
|
+
function normalizeText(value) {
|
|
6
|
+
return String(value || "")
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/["'“”‘’]/g, "")
|
|
9
|
+
.replace(/[^a-z0-9\s/.-]/g, " ")
|
|
10
|
+
.replace(/\s+/g, " ")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toEpochDay(isoDate) {
|
|
15
|
+
if (!isoDate) return null;
|
|
16
|
+
const ts = Date.parse(`${isoDate}T00:00:00Z`);
|
|
17
|
+
return Number.isFinite(ts) ? Math.floor(ts / 86400000) : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeEventRecord(record) {
|
|
21
|
+
const eventDate = record && record.event_date ? record.event_date : null;
|
|
22
|
+
const mentionDate = record && record.mention_date ? record.mention_date : null;
|
|
23
|
+
const observedAt = record && record.observed_at ? record.observed_at : (mentionDate || null);
|
|
24
|
+
const referencedAt = record && record.referenced_at ? record.referenced_at : (eventDate || mentionDate || null);
|
|
25
|
+
return {
|
|
26
|
+
...record,
|
|
27
|
+
title: String(record && record.title ? record.title : "").trim(),
|
|
28
|
+
event_type: String(record && record.event_type ? record.event_type : "other").trim() || "other",
|
|
29
|
+
event_kind: String(record && (record.event_kind || record.event_type) ? (record.event_kind || record.event_type) : "other").trim() || "other",
|
|
30
|
+
aliases: Array.isArray(record && record.aliases) ? record.aliases.filter(Boolean).map(String) : [],
|
|
31
|
+
entities: Array.isArray(record && record.entities) ? record.entities.filter(Boolean).map(String) : [],
|
|
32
|
+
event_date: eventDate,
|
|
33
|
+
mention_date: mentionDate,
|
|
34
|
+
observed_at: observedAt,
|
|
35
|
+
referenced_at: referencedAt,
|
|
36
|
+
relative_offset: record && record.relative_offset ? String(record.relative_offset) : null,
|
|
37
|
+
event_phase: record && record.event_phase ? String(record.event_phase) : null,
|
|
38
|
+
time_granularity: record && record.time_granularity ? String(record.time_granularity) : "day",
|
|
39
|
+
temporal_confidence: record && typeof record.temporal_confidence === "number" ? record.temporal_confidence : null,
|
|
40
|
+
source_span: record && record.source_span ? String(record.source_span) : null,
|
|
41
|
+
epoch_day: record && typeof record.epoch_day === "number" ? record.epoch_day : toEpochDay(referencedAt),
|
|
42
|
+
date_source: record && record.date_source ? String(record.date_source) : "implicit",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function dedupeEvents(events) {
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const event of (events || []).map(normalizeEventRecord)) {
|
|
50
|
+
const key = [
|
|
51
|
+
event.event_type,
|
|
52
|
+
event.event_kind,
|
|
53
|
+
normalizeText(event.title),
|
|
54
|
+
event.event_date || "",
|
|
55
|
+
event.mention_date || "",
|
|
56
|
+
].join("|");
|
|
57
|
+
if (seen.has(key)) continue;
|
|
58
|
+
seen.add(key);
|
|
59
|
+
out.push(event);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function scoreEventMatch(event, anchor, options = {}) {
|
|
65
|
+
const anchorNorm = normalizeText(anchor);
|
|
66
|
+
if (!anchorNorm) return 0;
|
|
67
|
+
|
|
68
|
+
const titleNorm = normalizeText(event.title);
|
|
69
|
+
const aliasNorms = event.aliases.map(normalizeText);
|
|
70
|
+
const entityNorms = event.entities.map(normalizeText);
|
|
71
|
+
let score = 0;
|
|
72
|
+
|
|
73
|
+
if (titleNorm === anchorNorm) score += 100;
|
|
74
|
+
else if (titleNorm.includes(anchorNorm) || anchorNorm.includes(titleNorm)) score += 45;
|
|
75
|
+
|
|
76
|
+
for (const alias of aliasNorms) {
|
|
77
|
+
if (alias === anchorNorm) score += 80;
|
|
78
|
+
else if (alias.includes(anchorNorm) || anchorNorm.includes(alias)) score += 25;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const entity of entityNorms) {
|
|
82
|
+
if (entity === anchorNorm) score += 50;
|
|
83
|
+
else if (entity.includes(anchorNorm) || anchorNorm.includes(entity)) score += 15;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (options.type && event.event_type === options.type) score += 15;
|
|
87
|
+
if (options.kind && event.event_kind === options.kind) score += 15;
|
|
88
|
+
if (options.semantics === "acquired" && event.event_type === "preorder") score -= 1000;
|
|
89
|
+
if (options.preferEventPhase && event.event_phase === options.preferEventPhase) score += 12;
|
|
90
|
+
if (event.event_phase === "preparation" && !/\bprepar/i.test(anchorNorm)) score -= 18;
|
|
91
|
+
if (event.event_phase === "scheduled") score += 4;
|
|
92
|
+
|
|
93
|
+
if (event.date_source === "explicit") score += 10;
|
|
94
|
+
else if (event.date_source === "derived") score += 6;
|
|
95
|
+
else if (event.date_source === "mention") score += 2;
|
|
96
|
+
|
|
97
|
+
if (typeof event.temporal_confidence === "number") {
|
|
98
|
+
score += Math.round(event.temporal_confidence * 4);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return score;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveEvent(events, anchor, options = {}) {
|
|
105
|
+
const candidates = dedupeEvents(events).filter((event) => {
|
|
106
|
+
if (!event.title || event.epoch_day == null) return false;
|
|
107
|
+
if (options.type && event.event_type !== options.type) return false;
|
|
108
|
+
if (options.kind && event.event_kind !== options.kind) return false;
|
|
109
|
+
if (options.excludeType && event.event_type === options.excludeType) return false;
|
|
110
|
+
if (options.semantics === "acquired" && event.event_type === "preorder") return false;
|
|
111
|
+
return scoreEventMatch(event, anchor, options) > 0;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const ranked = candidates.sort((a, b) => {
|
|
115
|
+
const scoreDiff = scoreEventMatch(b, anchor, options) - scoreEventMatch(a, anchor, options);
|
|
116
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
117
|
+
if (options.preferLatest) return b.epoch_day - a.epoch_day;
|
|
118
|
+
return a.epoch_day - b.epoch_day;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return ranked[0] || null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function first(events, options = {}) {
|
|
125
|
+
return dedupeEvents(events)
|
|
126
|
+
.filter((event) => event.epoch_day != null)
|
|
127
|
+
.filter((event) => !options.type || event.event_type === options.type)
|
|
128
|
+
.filter((event) => !options.kind || event.event_kind === options.kind)
|
|
129
|
+
.sort((a, b) => a.epoch_day - b.epoch_day)[0] || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function last(events, options = {}) {
|
|
133
|
+
return dedupeEvents(events)
|
|
134
|
+
.filter((event) => event.epoch_day != null)
|
|
135
|
+
.filter((event) => !options.type || event.event_type === options.type)
|
|
136
|
+
.filter((event) => !options.kind || event.event_kind === options.kind)
|
|
137
|
+
.sort((a, b) => b.epoch_day - a.epoch_day)[0] || null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function firstAfter(events, query = {}) {
|
|
141
|
+
const anchor = query.anchor || first(events, { type: query.anchorType, kind: query.anchorKind });
|
|
142
|
+
if (!anchor || anchor.epoch_day == null) return null;
|
|
143
|
+
|
|
144
|
+
return dedupeEvents(events)
|
|
145
|
+
.filter((event) => event.epoch_day != null && event.epoch_day > anchor.epoch_day)
|
|
146
|
+
.filter((event) => !query.targetType || event.event_type === query.targetType)
|
|
147
|
+
.filter((event) => !query.targetKind || event.event_kind === query.targetKind)
|
|
148
|
+
.sort((a, b) => a.epoch_day - b.epoch_day)[0] || null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function daysBetween(left, right) {
|
|
152
|
+
if (!left || !right || left.epoch_day == null || right.epoch_day == null) return null;
|
|
153
|
+
return Math.abs(right.epoch_day - left.epoch_day);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function monthsSince(event, referenceDate) {
|
|
157
|
+
if (!event || !referenceDate) return null;
|
|
158
|
+
const [yearA, monthA] = String(event.referenced_at || event.event_date || event.mention_date || "").split("-").map(Number);
|
|
159
|
+
const [yearB, monthB] = String(referenceDate).split("-").map(Number);
|
|
160
|
+
if (!yearA || !monthA || !yearB || !monthB) return null;
|
|
161
|
+
return ((yearB - yearA) * 12) + (monthB - monthA);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function countBefore(events, pivot, options = {}) {
|
|
165
|
+
if (!pivot || pivot.epoch_day == null) return null;
|
|
166
|
+
return dedupeEvents(events)
|
|
167
|
+
.filter((event) => event.epoch_day != null && event.epoch_day < pivot.epoch_day)
|
|
168
|
+
.filter((event) => !options.type || event.event_type === options.type)
|
|
169
|
+
.filter((event) => !options.kind || event.event_kind === options.kind)
|
|
170
|
+
.length;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
normalizeText,
|
|
175
|
+
toEpochDay,
|
|
176
|
+
normalizeEventRecord,
|
|
177
|
+
dedupeEvents,
|
|
178
|
+
scoreEventMatch,
|
|
179
|
+
resolveEvent,
|
|
180
|
+
first,
|
|
181
|
+
last,
|
|
182
|
+
firstAfter,
|
|
183
|
+
daysBetween,
|
|
184
|
+
monthsSince,
|
|
185
|
+
countBefore,
|
|
186
|
+
};
|