@rtbnext/core 2.0.0-alpha.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/abstract/Cache.d.ts +9 -0
  4. package/dist/abstract/Cache.js +19 -0
  5. package/dist/abstract/Index.d.ts +22 -0
  6. package/dist/abstract/Index.js +81 -0
  7. package/dist/abstract/Job.d.ts +19 -0
  8. package/dist/abstract/Job.js +49 -0
  9. package/dist/abstract/Snapshot.d.ts +22 -0
  10. package/dist/abstract/Snapshot.js +78 -0
  11. package/dist/bin/cli.d.ts +2 -0
  12. package/dist/bin/cli.js +25 -0
  13. package/dist/bin/cron.d.ts +2 -0
  14. package/dist/bin/cron.js +4 -0
  15. package/dist/core/Config.d.ts +30 -0
  16. package/dist/core/Config.js +66 -0
  17. package/dist/core/Cron.d.ts +12 -0
  18. package/dist/core/Cron.js +52 -0
  19. package/dist/core/Fetch.d.ts +28 -0
  20. package/dist/core/Fetch.js +172 -0
  21. package/dist/core/Logger.d.ts +30 -0
  22. package/dist/core/Logger.js +92 -0
  23. package/dist/core/Queue.d.ts +37 -0
  24. package/dist/core/Queue.js +136 -0
  25. package/dist/core/Storage.d.ts +28 -0
  26. package/dist/core/Storage.js +166 -0
  27. package/dist/core/Utils.d.ts +33 -0
  28. package/dist/core/Utils.js +167 -0
  29. package/dist/interfaces/cache.d.ts +6 -0
  30. package/dist/interfaces/config.d.ts +21 -0
  31. package/dist/interfaces/cron.d.ts +3 -0
  32. package/dist/interfaces/fetch.d.ts +13 -0
  33. package/dist/interfaces/filter.d.ts +12 -0
  34. package/dist/interfaces/index.d.ts +30 -0
  35. package/dist/interfaces/job.d.ts +9 -0
  36. package/dist/interfaces/list.d.ts +9 -0
  37. package/dist/interfaces/logger.d.ts +20 -0
  38. package/dist/interfaces/mover.d.ts +7 -0
  39. package/dist/interfaces/parser.d.ts +68 -0
  40. package/dist/interfaces/profile.d.ts +30 -0
  41. package/dist/interfaces/queue.d.ts +17 -0
  42. package/dist/interfaces/snapshot.d.ts +16 -0
  43. package/dist/interfaces/stats.d.ts +45 -0
  44. package/dist/interfaces/storage.d.ts +16 -0
  45. package/dist/job/Alias.d.ts +8 -0
  46. package/dist/job/Alias.js +42 -0
  47. package/dist/job/Annual.d.ts +8 -0
  48. package/dist/job/Annual.js +41 -0
  49. package/dist/job/List.d.ts +11 -0
  50. package/dist/job/List.js +101 -0
  51. package/dist/job/Merge.d.ts +10 -0
  52. package/dist/job/Merge.js +59 -0
  53. package/dist/job/Move.d.ts +7 -0
  54. package/dist/job/Move.js +33 -0
  55. package/dist/job/Performance.d.ts +8 -0
  56. package/dist/job/Performance.js +27 -0
  57. package/dist/job/Profile.d.ts +11 -0
  58. package/dist/job/Profile.js +76 -0
  59. package/dist/job/Queue.d.ts +8 -0
  60. package/dist/job/Queue.js +54 -0
  61. package/dist/job/RTB.d.ts +12 -0
  62. package/dist/job/RTB.js +121 -0
  63. package/dist/job/Stats.d.ts +11 -0
  64. package/dist/job/Stats.js +46 -0
  65. package/dist/job/Top10.d.ts +9 -0
  66. package/dist/job/Top10.js +48 -0
  67. package/dist/job/Wiki.d.ts +9 -0
  68. package/dist/job/Wiki.js +40 -0
  69. package/dist/job/index.d.ts +26 -0
  70. package/dist/job/index.js +26 -0
  71. package/dist/lib/const.d.ts +31 -0
  72. package/dist/lib/const.js +74 -0
  73. package/dist/lib/list.d.ts +90 -0
  74. package/dist/lib/list.js +72 -0
  75. package/dist/lib/regex.d.ts +7 -0
  76. package/dist/lib/regex.js +7 -0
  77. package/dist/model/Filter.d.ts +28 -0
  78. package/dist/model/Filter.js +122 -0
  79. package/dist/model/List.d.ts +12 -0
  80. package/dist/model/List.js +43 -0
  81. package/dist/model/ListIndex.d.ts +8 -0
  82. package/dist/model/ListIndex.js +10 -0
  83. package/dist/model/Mover.d.ts +15 -0
  84. package/dist/model/Mover.js +74 -0
  85. package/dist/model/Profile.d.ts +49 -0
  86. package/dist/model/Profile.js +181 -0
  87. package/dist/model/ProfileIndex.d.ts +20 -0
  88. package/dist/model/ProfileIndex.js +140 -0
  89. package/dist/model/Stats.d.ts +56 -0
  90. package/dist/model/Stats.js +435 -0
  91. package/dist/parser/BillionairesListParser.d.ts +3 -0
  92. package/dist/parser/BillionairesListParser.js +2 -0
  93. package/dist/parser/ListParser.d.ts +7 -0
  94. package/dist/parser/ListParser.js +11 -0
  95. package/dist/parser/Parser.d.ts +43 -0
  96. package/dist/parser/Parser.js +146 -0
  97. package/dist/parser/PersonListParser.d.ts +29 -0
  98. package/dist/parser/PersonListParser.js +111 -0
  99. package/dist/parser/ProfileParser.d.ts +44 -0
  100. package/dist/parser/ProfileParser.js +193 -0
  101. package/dist/parser/RTBListParser.d.ts +15 -0
  102. package/dist/parser/RTBListParser.js +91 -0
  103. package/dist/types/annual.d.ts +7 -0
  104. package/dist/types/config.d.ts +35 -0
  105. package/dist/types/fetch.d.ts +3 -0
  106. package/dist/types/generic.d.ts +10 -0
  107. package/dist/types/job.d.ts +71 -0
  108. package/dist/types/list.d.ts +49 -0
  109. package/dist/types/parser.d.ts +7 -0
  110. package/dist/types/profile.d.ts +9 -0
  111. package/dist/types/queue.d.ts +15 -0
  112. package/dist/types/response.d.ts +183 -0
  113. package/dist/types/storage.d.ts +3 -0
  114. package/dist/types/wiki.d.ts +1 -0
  115. package/dist/utils/Annual.d.ts +7 -0
  116. package/dist/utils/Annual.js +99 -0
  117. package/dist/utils/Performance.d.ts +8 -0
  118. package/dist/utils/Performance.js +39 -0
  119. package/dist/utils/ProfileManager.d.ts +24 -0
  120. package/dist/utils/ProfileManager.js +60 -0
  121. package/dist/utils/ProfileMerger.d.ts +11 -0
  122. package/dist/utils/ProfileMerger.js +67 -0
  123. package/dist/utils/Ranking.d.ts +11 -0
  124. package/dist/utils/Ranking.js +77 -0
  125. package/dist/utils/Wiki.d.ts +11 -0
  126. package/dist/utils/Wiki.js +168 -0
  127. package/package.json +45 -0
@@ -0,0 +1,74 @@
1
+ import { Snapshot } from '../abstract/Snapshot.js';
2
+ import { Utils } from '../core/Utils.js';
3
+ import { Parser } from '../parser/Parser.js';
4
+ export class Mover extends Snapshot {
5
+ static instance;
6
+ constructor() {
7
+ super('mover');
8
+ }
9
+ // --- parse mover items ---
10
+ prepItems(items, method, dir) {
11
+ return items
12
+ .sort((a, b) => (dir === 'asc' ? a.value - b.value : b.value - a.value))
13
+ .slice(0, 10)
14
+ .map(({ uri, name, value }) => ({ uri, name, value: Parser[method](value) }));
15
+ }
16
+ parseEntry(entry, method) {
17
+ return { winner: this.prepItems(entry.winner, method, 'desc'), loser: this.prepItems(entry.loser, method, 'asc') };
18
+ }
19
+ parseBucket(bucket) {
20
+ return {
21
+ total: Parser.container({
22
+ value: { value: bucket.total.value, type: 'money' },
23
+ percent: { value: bucket.total.percent, type: 'pct' }
24
+ }),
25
+ networth: this.parseEntry(bucket.networth, 'money'),
26
+ percent: this.parseEntry(bucket.percent, 'pct')
27
+ };
28
+ }
29
+ // --- (override) save mover snapshot ---
30
+ saveSnapshot(snapshot, force) {
31
+ return super.saveSnapshot(
32
+ {
33
+ ...Utils.metaData(),
34
+ date: Parser.date(snapshot.date),
35
+ today: this.parseBucket(snapshot.today),
36
+ ytd: this.parseBucket(snapshot.ytd)
37
+ },
38
+ force
39
+ );
40
+ }
41
+ // --- instantiate ---
42
+ static getInstance() {
43
+ return (this.instance ??= new Mover());
44
+ }
45
+ // --- factory ---
46
+ static factory(date) {
47
+ return {
48
+ date: Parser.date(date ?? new Date(), 'ymd'),
49
+ today: {
50
+ total: { value: 0, percent: 0 },
51
+ networth: { winner: [], loser: [] },
52
+ percent: { winner: [], loser: [] }
53
+ },
54
+ ytd: { total: { value: 0, percent: 0 }, networth: { winner: [], loser: [] }, percent: { winner: [], loser: [] } }
55
+ };
56
+ }
57
+ // --- aggregate mover data ---
58
+ static aggregate(data, uri, name, mover, total = 0) {
59
+ if (data?.today?.value) {
60
+ const type = data.today.value > 0 ? 'winner' : 'loser';
61
+ mover.today.total.value += data.today.value;
62
+ mover.today.total.percent = total ? (mover.today.total.value / total) * 100 : 0;
63
+ mover.today.networth[type].push({ uri, name, value: data.today.value });
64
+ mover.today.percent[type].push({ uri, name, value: data.today.percent });
65
+ }
66
+ if (data?.ytd?.value) {
67
+ const type = data.ytd.value > 0 ? 'winner' : 'loser';
68
+ mover.ytd.total.value += data.ytd.value;
69
+ mover.ytd.total.percent = total ? (mover.ytd.total.value / total) * 100 : 0;
70
+ mover.ytd.networth[type].push({ uri, name, value: data.ytd.value });
71
+ mover.ytd.percent[type].push({ uri, name, value: data.ytd.percent });
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,49 @@
1
+ import { ArrayMode } from '@komed3/deepmerge';
2
+ import type {
3
+ TProfileData,
4
+ TProfileHistory,
5
+ TProfileHistoryItem,
6
+ TProfileIndexItem,
7
+ TProfileMetaData
8
+ } from '@rtbnext/schema/src/model/profile';
9
+ import type { IProfile } from '../interfaces/profile';
10
+ export declare class Profile implements IProfile {
11
+ private static readonly storage;
12
+ private static readonly index;
13
+ private touched;
14
+ private uri;
15
+ private path;
16
+ private meta;
17
+ private data?;
18
+ private history?;
19
+ private constructor();
20
+ private resolvePath;
21
+ private metaData;
22
+ private resolveHistory;
23
+ getUri(): string;
24
+ getMeta(): TProfileMetaData['$metadata'];
25
+ schemaVersion(): number;
26
+ lastModified(): string;
27
+ lastModifiedTime(): number;
28
+ lastLookup(): string | undefined;
29
+ lastLookupTime(): number | undefined;
30
+ verify(id: string): boolean;
31
+ touch(): void;
32
+ touchLookup(): void;
33
+ needSave(): boolean;
34
+ save(syncIndex?: boolean): void;
35
+ getData(): TProfileData;
36
+ setData(data: TProfileData): void;
37
+ updateData(data: Partial<TProfileData>, mode?: ArrayMode): void;
38
+ getHistory(): TProfileHistory;
39
+ setHistory(history: TProfileHistory): void;
40
+ addHistory(row: TProfileHistoryItem): void;
41
+ mergeHistory(history: TProfileHistory): void;
42
+ move(uriLike: string, makeAlias?: boolean): boolean;
43
+ static factory(data?: Partial<TProfileData>): Partial<TProfileData>;
44
+ static get(uriLike: string): IProfile | false;
45
+ static getByItem(item: TProfileIndexItem): IProfile | false;
46
+ static find(uriLike: string): IProfile | false;
47
+ static create(uriLike: string, data: TProfileData, history?: TProfileHistory, lookup?: boolean): Profile | false;
48
+ static delete(uriLike: string): boolean;
49
+ }
@@ -0,0 +1,181 @@
1
+ import { join } from 'node:path';
2
+ import { log } from '../core/Logger.js';
3
+ import { Storage } from '../core/Storage.js';
4
+ import { Utils } from '../core/Utils.js';
5
+ import { ProfileIndex } from './ProfileIndex.js';
6
+ export class Profile {
7
+ static storage = Storage.getInstance();
8
+ static index = ProfileIndex.getInstance();
9
+ touched = false;
10
+ uri;
11
+ path;
12
+ meta;
13
+ data;
14
+ history;
15
+ constructor(item) {
16
+ if (!item || !item.uri) throw new Error('No valid profile index item given');
17
+ this.uri = item.uri;
18
+ this.path = join('profile', item.uri);
19
+ Profile.storage.ensurePath(this.path, true);
20
+ this.meta = this.metaData();
21
+ }
22
+ // --- helper ---
23
+ resolvePath(path) {
24
+ return join(this.path, path);
25
+ }
26
+ metaData() {
27
+ return Profile.storage.readJSON(this.resolvePath('meta.json')) || Utils.metaData();
28
+ }
29
+ resolveHistory(...history) {
30
+ return [...new Map([...this.getHistory(), ...history].map(i => [i[0], i])).values()].sort((a, b) =>
31
+ a[0].localeCompare(b[0])
32
+ );
33
+ }
34
+ // --- public getter ---
35
+ getUri() {
36
+ return this.uri;
37
+ }
38
+ getMeta() {
39
+ return this.meta.$metadata;
40
+ }
41
+ schemaVersion() {
42
+ return this.meta.$metadata.schemaVersion;
43
+ }
44
+ lastModified() {
45
+ return this.meta.$metadata.lastModified;
46
+ }
47
+ lastModifiedTime() {
48
+ return new Date(this.meta.$metadata.lastModified).getTime();
49
+ }
50
+ lastLookup() {
51
+ return this.meta.$metadata.lastLookup;
52
+ }
53
+ lastLookupTime() {
54
+ return this.meta.$metadata.lastLookup ? new Date(this.meta.$metadata.lastLookup).getTime() : undefined;
55
+ }
56
+ // --- verify profile ---
57
+ verify(id) {
58
+ return Utils.verifyHash(id, this.getData().id);
59
+ }
60
+ // --- work flow ---
61
+ touch() {
62
+ this.meta.$metadata.lastModified = Utils.date('iso');
63
+ this.touched = true;
64
+ }
65
+ touchLookup() {
66
+ this.touch();
67
+ this.meta.$metadata.lastLookup = this.meta.$metadata.lastModified;
68
+ }
69
+ needSave() {
70
+ return this.touched;
71
+ }
72
+ save(syncIndex = true) {
73
+ if (!this.touched) return;
74
+ log.debug(`Saving profile: ${this.uri}`);
75
+ log.catch(() => {
76
+ if (syncIndex && this.data && !Profile.index.syncFromData(this.data))
77
+ throw new Error('Failed to update profile index');
78
+ if (this.data && !Profile.storage.writeJSON(this.resolvePath('profile.json'), this.data))
79
+ throw new Error('Failed to write profile data');
80
+ if (this.history && !Profile.storage.writeCSV(this.resolvePath('history.csv'), this.history))
81
+ throw new Error('Failed to write profile history');
82
+ if (this.meta && !Profile.storage.writeJSON(this.resolvePath('meta.json'), this.meta))
83
+ throw new Error('Failed to write profile metadata');
84
+ this.touched = false;
85
+ }, `Failed to save profile: ${this.uri}`);
86
+ }
87
+ // --- profile data ---
88
+ getData() {
89
+ return (this.data ??= Profile.storage.readJSON(this.resolvePath('profile.json')) ?? Profile.factory());
90
+ }
91
+ setData(data) {
92
+ this.data = Profile.factory(data);
93
+ this.touch();
94
+ }
95
+ updateData(data, mode = 'replace' /* ArrayMode.Replace */) {
96
+ this.data = Utils.merge(mode, this.getData(), data);
97
+ this.touch();
98
+ }
99
+ // --- profile history ---
100
+ getHistory() {
101
+ return (this.history ??= Profile.storage.readCSV(this.resolvePath('history.csv')) ?? []);
102
+ }
103
+ setHistory(history) {
104
+ this.history = history;
105
+ this.touch();
106
+ }
107
+ addHistory(row) {
108
+ this.setHistory(this.resolveHistory(row));
109
+ }
110
+ mergeHistory(history) {
111
+ this.setHistory(this.resolveHistory(...history));
112
+ }
113
+ // --- move profile ---
114
+ move(uriLike, makeAlias = true) {
115
+ const uri = Utils.sanitize(uriLike);
116
+ log.debug(`Moving profile: ${this.uri} -> ${uri}`);
117
+ return (
118
+ log.catch(() => {
119
+ const item = Profile.index.move(this.uri, uri, makeAlias);
120
+ if (!item) throw new Error('Failed to move profile index item');
121
+ const oldPath = this.path;
122
+ this.uri = uri;
123
+ this.path = join('profile', uri);
124
+ if (!Profile.storage.move(oldPath, this.path)) throw new Error('Failed to move profile storage');
125
+ this.updateData({ uri: uri });
126
+ this.save(false);
127
+ return true;
128
+ }, `Failed to move profile: ${this.uri} -> ${uri}`) ?? false
129
+ );
130
+ }
131
+ // --- factory ---
132
+ static factory(data) {
133
+ return { ...{ info: {}, bio: {}, related: [], media: [], ranking: [], annual: [], assets: [] }, ...data };
134
+ }
135
+ // --- instantiate ---
136
+ static get(uriLike) {
137
+ return log.catch(() => new Profile(Profile.index.get(uriLike)), `Failed to get profile: ${uriLike}`) ?? false;
138
+ }
139
+ static getByItem(item) {
140
+ return log.catch(() => new Profile(item), `Failed to get profile by item: ${item.uri}`) ?? false;
141
+ }
142
+ static find(uriLike) {
143
+ return (
144
+ log.catch(
145
+ () => new Profile(Profile.index.find(uriLike).values().next().value),
146
+ `Failed to find profile: ${uriLike}`
147
+ ) ?? false
148
+ );
149
+ }
150
+ // --- create profile ---
151
+ static create(uriLike, data, history, lookup = false) {
152
+ const uri = Utils.sanitize(uriLike);
153
+ log.debug(`Creating profile: ${uri}`);
154
+ return (
155
+ log.catch(() => {
156
+ if (!Profile.index.isAliasAvailable(uri)) throw new Error(`Profile URI ${uri} is already taken`);
157
+ const profile = new Profile({ uri });
158
+ profile.setData(Profile.factory(data));
159
+ profile.setHistory(history ?? []);
160
+ if (lookup) profile.touchLookup();
161
+ profile.save();
162
+ log.debug(`Profile created: ${uri}`);
163
+ return profile;
164
+ }, `Failed to create profile: ${uri}`) ?? false
165
+ );
166
+ }
167
+ // --- delete profile ---
168
+ static delete(uriLike) {
169
+ const uri = Utils.sanitize(uriLike);
170
+ log.debug(`Deleting profile: ${uri}`);
171
+ return (
172
+ log.catch(() => {
173
+ const path = join('profile', uri);
174
+ if (!Profile.storage.remove(path)) throw new Error('Failed to remove profile from storage');
175
+ Profile.index.delete(uri);
176
+ log.debug(`Profile deleted: ${uri}`);
177
+ return true;
178
+ }, `Failed to delete profile: ${uri}`) ?? false
179
+ );
180
+ }
181
+ }
@@ -0,0 +1,20 @@
1
+ import type { TProfileData, TProfileIndex, TProfileIndexItem } from '@rtbnext/schema/src/model/profile';
2
+ import { Index } from '../abstract/Index.js';
3
+ import type { IProfileIndex } from '../interfaces/index';
4
+ export declare class ProfileIndex extends Index<TProfileIndexItem, TProfileIndex> implements IProfileIndex {
5
+ protected static instance: IProfileIndex;
6
+ private constructor();
7
+ private sanitizeAliases;
8
+ private getUriByAlias;
9
+ private assertAvailableAlias;
10
+ private resolveAliases;
11
+ find(uriLike: string): TProfileIndex;
12
+ move(from: string, to: string, makeAlias?: boolean): TProfileIndexItem | false;
13
+ syncFromData(data: TProfileData): TProfileIndexItem | false;
14
+ hasAlias(aliasLike: string, uriLike?: string): string | false;
15
+ isAliasAvailable(aliasLike: string): boolean;
16
+ removeAlias(aliasLike: string): boolean;
17
+ addAliases(uriLike: string, ...aliases: string[]): TProfileIndexItem | false;
18
+ rmvAliases(uriLike: string, ...aliases: string[]): TProfileIndexItem | false;
19
+ static getInstance(): IProfileIndex;
20
+ }
@@ -0,0 +1,140 @@
1
+ import { Index } from '../abstract/Index.js';
2
+ import { log } from '../core/Logger.js';
3
+ import { Utils } from '../core/Utils.js';
4
+ export class ProfileIndex extends Index {
5
+ static instance;
6
+ constructor() {
7
+ super('profile', 'profile/index.json');
8
+ }
9
+ // --- alias helper ---
10
+ sanitizeAliases(aliases) {
11
+ return aliases.map(a => Utils.sanitize(a)).filter(Boolean);
12
+ }
13
+ getUriByAlias(alias) {
14
+ return [...this.index.values()].find(({ aliases }) => aliases.includes(alias))?.uri || false;
15
+ }
16
+ assertAvailableAlias(alias, whitelist = []) {
17
+ if (this.has(alias)) throw new Error(`Alias ${alias} conflicts with existing profile URI`);
18
+ const owner = this.getUriByAlias(alias);
19
+ if (owner && !whitelist.includes(owner)) throw new Error(`Alias ${alias} already exists for profile ${owner}`);
20
+ }
21
+ resolveAliases(uri, aliases = [], add = [], rmv = []) {
22
+ for (const a of add) this.assertAvailableAlias(a, [uri]);
23
+ return Utils.mergeArray(
24
+ aliases.filter(alias => !rmv.includes(alias)),
25
+ add,
26
+ 'unique' /* ArrayMode.Unique */
27
+ );
28
+ }
29
+ // --- special profile index operations ---
30
+ find(uriLike) {
31
+ const uri = Utils.sanitize(uriLike);
32
+ return new Map([...this.index].filter(([key, { aliases }]) => key === uri || aliases.includes(uri)));
33
+ }
34
+ move(from, to, makeAlias = true) {
35
+ log.debug(`Moving profile index item from ${from} to ${to}`);
36
+ return (
37
+ log.catch(() => {
38
+ ((from = Utils.sanitize(from)), (to = Utils.sanitize(to)));
39
+ const target = this.index.get(from),
40
+ match = this.find(to);
41
+ if (!target || match.size > 1) throw new Error('Invalid move operation');
42
+ const foundKey = match.keys().next().value;
43
+ if (foundKey && foundKey !== from) throw new Error('Destination already exists');
44
+ const item = {
45
+ ...target,
46
+ uri: to,
47
+ aliases: this.resolveAliases(to, target.aliases, makeAlias ? [from] : [], [to])
48
+ };
49
+ this.index.delete(from);
50
+ this.index.set(to, item);
51
+ this.saveIndex();
52
+ return item;
53
+ }, `Failed to move profile index item ${from} to ${to}`) ?? false
54
+ );
55
+ }
56
+ syncFromData(data) {
57
+ const {
58
+ uri,
59
+ info: {
60
+ name: { shortName: name }
61
+ },
62
+ bio: { cv },
63
+ wiki: { desc, image } = {}
64
+ } = data;
65
+ const item = this.get(uri);
66
+ return this.update(uri, {
67
+ ...item,
68
+ uri,
69
+ name,
70
+ desc,
71
+ image: image?.thumb ?? image?.file,
72
+ aliases: this.resolveAliases(uri, item?.aliases),
73
+ text: Utils.buildSearchText(cv)
74
+ });
75
+ }
76
+ // --- alias handling ---
77
+ hasAlias(aliasLike, uriLike) {
78
+ const owner = this.getUriByAlias(Utils.sanitize(aliasLike));
79
+ if (uriLike !== undefined && owner !== Utils.sanitize(uriLike)) return false;
80
+ return owner;
81
+ }
82
+ isAliasAvailable(aliasLike) {
83
+ try {
84
+ this.assertAvailableAlias(Utils.sanitize(aliasLike));
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ removeAlias(aliasLike) {
91
+ const alias = Utils.sanitize(aliasLike);
92
+ log.debug(`Removing profile alias ${alias}`);
93
+ return (
94
+ log.catch(() => {
95
+ for (const item of this.index.values()) {
96
+ const index = item.aliases.indexOf(alias);
97
+ if (index >= 0) {
98
+ item.aliases.splice(index, 1);
99
+ this.saveIndex();
100
+ log.debug(`Removed alias ${alias} from profile index item ${item.uri}`);
101
+ return true;
102
+ }
103
+ }
104
+ return false;
105
+ }, `Failed to remove profile alias ${alias}`) ?? false
106
+ );
107
+ }
108
+ addAliases(uriLike, ...aliases) {
109
+ const uri = Utils.sanitize(uriLike);
110
+ log.debug(`Adding profile aliases [${aliases.join(', ')}] to ${uri}`);
111
+ return (
112
+ log.catch(() => {
113
+ const item = this.index.get(uri);
114
+ if (!item) throw new Error(`Profile index item ${uri} not found`);
115
+ if (!aliases.length) return item;
116
+ item.aliases = this.resolveAliases(uri, item.aliases, this.sanitizeAliases(aliases));
117
+ this.saveIndex();
118
+ return item;
119
+ }, `Failed to add profile aliases to ${uri}`) ?? false
120
+ );
121
+ }
122
+ rmvAliases(uriLike, ...aliases) {
123
+ const uri = Utils.sanitize(uriLike);
124
+ log.debug(`Removing profile aliases [${aliases.join(', ')}] from ${uri}`);
125
+ return (
126
+ log.catch(() => {
127
+ const item = this.index.get(uri);
128
+ if (!item) throw new Error(`Profile index item ${uri} not found`);
129
+ if (!aliases.length) return item;
130
+ item.aliases = this.resolveAliases(uri, item.aliases, [], this.sanitizeAliases(aliases));
131
+ this.saveIndex();
132
+ return item;
133
+ }, `Failed to remove profile aliases from ${uri}`) ?? false
134
+ );
135
+ }
136
+ // --- instantitate ---
137
+ static getInstance() {
138
+ return (ProfileIndex.instance ??= new ProfileIndex());
139
+ }
140
+ }
@@ -0,0 +1,56 @@
1
+ import type { TStatsGroup as TStatsGroupType } from '@rtbnext/schema/src/base/const';
2
+ import type { TListSnapshot, TPersonListItem } from '@rtbnext/schema/src/model/list';
3
+ import type { TProfileData } from '@rtbnext/schema/src/model/profile';
4
+ import type {
5
+ TDBStats,
6
+ TDBStatsData,
7
+ TGlobalStats,
8
+ TGlobalStatsData,
9
+ THistory,
10
+ TProfileStats,
11
+ TProfileStatsData,
12
+ TScatter,
13
+ TScatterItem,
14
+ TStatsGroup,
15
+ TStatsGroupItem,
16
+ TTop10,
17
+ TTop10Data,
18
+ TTop10List,
19
+ TWealthStats,
20
+ TWealthStatsData
21
+ } from '@rtbnext/schema/src/model/stats';
22
+ import type { IStats } from '../interfaces/stats';
23
+ export declare class Stats implements IStats {
24
+ private static readonly storage;
25
+ private static instance;
26
+ private constructor();
27
+ private initDB;
28
+ private resolvePath;
29
+ private prepStats;
30
+ private getStats;
31
+ private saveStats;
32
+ getGlobalStats(): TGlobalStats;
33
+ getProfileStats(): TProfileStats;
34
+ getWealthStats(): TWealthStats;
35
+ getScatter(): TScatter;
36
+ getTop10(): TTop10;
37
+ getDBStats(): TDBStats;
38
+ getHistory(): THistory;
39
+ getGroupedStatsIndex(group: TStatsGroupType): TStatsGroup<string>['index'];
40
+ getGroupedStatsHistory(group: TStatsGroupType, key: string): THistory;
41
+ getGroupedStats(group: TStatsGroupType): TStatsGroup<string>;
42
+ setGlobalStats(data: TGlobalStatsData): boolean;
43
+ setProfileStats(data: TProfileStatsData): boolean;
44
+ setWealthStats(data: TWealthStatsData): boolean;
45
+ setScatter(items: TScatterItem[]): boolean;
46
+ setTop10(data: TTop10Data): boolean;
47
+ updateTop10(key: string, list: TTop10List): boolean;
48
+ setDBStats(data: TDBStatsData): boolean;
49
+ setGroupedStats<T extends string = string>(group: TStatsGroupType, raw: Record<T, TStatsGroupItem>): boolean;
50
+ updateHistory(data: Partial<TGlobalStats>): boolean;
51
+ generateWealthStats(scatter: TScatterItem[]): boolean;
52
+ generateTop10Entry(snapshot: TListSnapshot<TPersonListItem>): boolean;
53
+ generateDBStats(): boolean;
54
+ static getInstance(): IStats;
55
+ static aggregate(data: TProfileData, date: string, stats: any): void;
56
+ }