xindex 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/research/2026-04-10-file-watching.md +79 -0
- package/.ai/research/2026-04-10-mcp-output-format.md +129 -0
- package/.ai/task/INDEX.md +12 -0
- package/.ai/task/done/INDEX.md +3 -0
- package/.ai/task/done/task.2026-04-09-local-ai-research-protos.log.md +98 -0
- package/.ai/task/done/task.2026-04-09-local-ai-research-protos.md +102 -0
- package/.ai/task/task.2026-04-10-cluster-config.log.md +19 -0
- package/.ai/task/task.2026-04-10-cluster-config.md +118 -0
- package/.ai/task/task.2026-04-10-dir-indexing.log.md +8 -0
- package/.ai/task/task.2026-04-10-dir-indexing.md +92 -0
- package/.ai/task/task.2026-04-10-line-clustering.log.md +50 -0
- package/.ai/task/task.2026-04-10-line-clustering.md +176 -0
- package/.ai/task/task.2026-04-10-object-store.log.md +7 -0
- package/.ai/task/task.2026-04-10-object-store.md +81 -0
- package/.ai/task/task.2026-04-10-search-config.log.md +46 -0
- package/.ai/task/task.2026-04-10-search-config.md +274 -0
- package/.ai/task/task.2026-04-10-watch-indexing.log.md +32 -0
- package/.ai/task/task.2026-04-10-watch-indexing.md +101 -0
- package/.ai/task/task.2026-04-10-xindex-mcp.log.md +5 -0
- package/.ai/task/task.2026-04-10-xindex-mcp.md +92 -0
- package/.ai/task/task.2026-04-10-xindex-mcp.report.md +113 -0
- package/.claude/settings.local.json +73 -0
- package/.claude/skills/make-hof/SKILL.md +8 -0
- package/.claude/skills/make-hof/playbook.md +38 -0
- package/.cursor/mcp.json +8 -0
- package/.mcp.json +8 -0
- package/.xindex.json +22 -0
- package/CLAUDE.md +54 -0
- package/README.md +206 -0
- package/apps/indexApp.ts +31 -0
- package/apps/mcpApp.ts +119 -0
- package/apps/run.index.ts +19 -0
- package/apps/run.mcp.ts +49 -0
- package/apps/run.reset.ts +10 -0
- package/apps/run.search.ts +21 -0
- package/apps/run.watch.ts +44 -0
- package/apps/searchApp.ts +9 -0
- package/apps/watchApp.ts +53 -0
- package/apps/watchFileEventsApp.ts +39 -0
- package/bin/xindex-index +2 -0
- package/bin/xindex-mcp +2 -0
- package/bin/xindex-reset +2 -0
- package/bin/xindex-search +2 -0
- package/bin/xindex-watch +2 -0
- package/componets/IType.ts +1 -0
- package/componets/appId.ts +3 -0
- package/componets/buildComponents.ts +27 -0
- package/componets/config/loadConfig.ts +43 -0
- package/componets/config/xindexConfig.ts +4 -0
- package/componets/index/contentIndexDriver.ts +39 -0
- package/componets/index/formatSearchResults.ts +18 -0
- package/componets/index/getIndexStats.ts +11 -0
- package/componets/index/handleFileEvent.ts +25 -0
- package/componets/index/indexApi.ts +45 -0
- package/componets/index/vectraIndex.ts +11 -0
- package/componets/index/watcherLock.ts +107 -0
- package/componets/keywords/cleanUpKeywords.ts +38 -0
- package/componets/keywords/extractKeywords.ts +14 -0
- package/componets/keywords/refineKeywords.ts +16 -0
- package/componets/llm/embed.ts +18 -0
- package/componets/llm/queryLLM.ts +20 -0
- package/componets/logger.ts +34 -0
- package/componets/walkFiles.ts +51 -0
- package/componets/watchFiles.ts +106 -0
- package/features/indexContent.ts +16 -0
- package/features/removeContent.ts +9 -0
- package/features/resetIndex.ts +9 -0
- package/features/searchIndex.ts +33 -0
- package/package.json +32 -0
- package/packages/fun/src/IType.ts +5 -0
- package/packages/fun/src/array-finder.ts +55 -0
- package/packages/fun/src/array-index.ts +35 -0
- package/packages/fun/src/array.ts +112 -0
- package/packages/fun/src/assert.ts +5 -0
- package/packages/fun/src/asyncRequest.ts +35 -0
- package/packages/fun/src/callsites.ts +18 -0
- package/packages/fun/src/case-never.ts +9 -0
- package/packages/fun/src/casting.ts +41 -0
- package/packages/fun/src/collect.ts +13 -0
- package/packages/fun/src/concurrency.ts +186 -0
- package/packages/fun/src/container.ts +86 -0
- package/packages/fun/src/counter.ts +45 -0
- package/packages/fun/src/create-map.ts +2 -0
- package/packages/fun/src/dedupe.ts +2 -0
- package/packages/fun/src/defer.ts +55 -0
- package/packages/fun/src/delay.ts +5 -0
- package/packages/fun/src/discriminate.ts +34 -0
- package/packages/fun/src/enum-values.ts +12 -0
- package/packages/fun/src/exponential-backoff.ts +20 -0
- package/packages/fun/src/flatten.ts +11 -0
- package/packages/fun/src/hash.ts +67 -0
- package/packages/fun/src/hash128.ts +6 -0
- package/packages/fun/src/hash256.ts +6 -0
- package/packages/fun/src/hub.ts +53 -0
- package/packages/fun/src/id.ts +10 -0
- package/packages/fun/src/interval.ts +76 -0
- package/packages/fun/src/is-non-nullable.ts +2 -0
- package/packages/fun/src/isIterable.ts +3 -0
- package/packages/fun/src/mailbox.ts +13 -0
- package/packages/fun/src/map-record.ts +19 -0
- package/packages/fun/src/match-collections.ts +57 -0
- package/packages/fun/src/match-left-and-right-arrays.ts +78 -0
- package/packages/fun/src/mem.ts +26 -0
- package/packages/fun/src/memos.ts +28 -0
- package/packages/fun/src/normalizeError.ts +25 -0
- package/packages/fun/src/nothing.ts +3 -0
- package/packages/fun/src/pipe.ts +18 -0
- package/packages/fun/src/prettyJson.ts +3 -0
- package/packages/fun/src/project.ts +8 -0
- package/packages/fun/src/promise.ts +27 -0
- package/packages/fun/src/pubsub.ts +128 -0
- package/packages/fun/src/randomId.ts +14 -0
- package/packages/fun/src/regexp-escape.ts +13 -0
- package/packages/fun/src/retry.ts +15 -0
- package/packages/fun/src/serial.test.ts +107 -0
- package/packages/fun/src/serial.ts +17 -0
- package/packages/fun/src/sleep.ts +3 -0
- package/packages/fun/src/sort-object.ts +46 -0
- package/packages/fun/src/speed-test.ts +56 -0
- package/packages/fun/src/tick.ts +37 -0
- package/packages/fun/src/time-behavior.ts +50 -0
- package/packages/fun/src/time.ts +22 -0
- package/packages/fun/src/timedFallback.ts +37 -0
- package/packages/fun/src/timer.ts +30 -0
- package/packages/fun/src/value.ts +33 -0
- package/packages/fun/src/waitForCounter.ts +15 -0
- package/packages/streamx/src/batch.ts +23 -0
- package/packages/streamx/src/batchTimed.ts +113 -0
- package/packages/streamx/src/buffer.ts +72 -0
- package/packages/streamx/src/concatenate.ts +33 -0
- package/packages/streamx/src/filter.ts +14 -0
- package/packages/streamx/src/flat.ts +19 -0
- package/packages/streamx/src/flatMap.ts +9 -0
- package/packages/streamx/src/from.ts +30 -0
- package/packages/streamx/src/index.ts +49 -0
- package/packages/streamx/src/interval.ts +58 -0
- package/packages/streamx/src/loop.ts +8 -0
- package/packages/streamx/src/map.ts +12 -0
- package/packages/streamx/src/merge.ts +89 -0
- package/packages/streamx/src/nodeReadable.ts +6 -0
- package/packages/streamx/src/nodeTransform.ts +9 -0
- package/packages/streamx/src/nodeWritable.ts +38 -0
- package/packages/streamx/src/objectReader.ts +16 -0
- package/packages/streamx/src/polyfill.ts +20 -0
- package/packages/streamx/src/reader.ts +38 -0
- package/packages/streamx/src/reduce.ts +15 -0
- package/packages/streamx/src/scale.ts +93 -0
- package/packages/streamx/src/scaleSync.ts +13 -0
- package/packages/streamx/src/sequence.ts +7 -0
- package/packages/streamx/src/tap.ts +9 -0
- package/packages/streamx/src/toArray.ts +9 -0
- package/packages/streamx/src/writer.ts +96 -0
- package/rnd/hf.ts +14 -0
- package/rnd/keywords-compromise.ts +18 -0
- package/rnd/keywords-pipeline.ts +79 -0
- package/rnd/keywords.ts +38 -0
- package/rnd/test-vectra-memory.ts +63 -0
- package/rnd/vectra-keywords.ts +95 -0
- package/rnd/vectra.ts +50 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Counter, ICounter } from './counter';
|
|
2
|
+
import { Defer, IDefer } from './defer';
|
|
3
|
+
import { IValue, Value } from './value';
|
|
4
|
+
import { waitForCounter, waitForZeroCounter } from './waitForCounter';
|
|
5
|
+
|
|
6
|
+
export type IStopKeepConcurrency = () => Promise<void>;
|
|
7
|
+
|
|
8
|
+
export function keepConcurrency(
|
|
9
|
+
quantity: IValue<number>,
|
|
10
|
+
fn: () => Promise<any>
|
|
11
|
+
): IStopKeepConcurrency {
|
|
12
|
+
const desired = Value<number>(quantity);
|
|
13
|
+
|
|
14
|
+
const counter = Counter();
|
|
15
|
+
const unsubscribeCounter = counter.subscribe(handle);
|
|
16
|
+
|
|
17
|
+
let stopDefer: undefined | IDefer = undefined;
|
|
18
|
+
let stopping = false;
|
|
19
|
+
|
|
20
|
+
function handle() {
|
|
21
|
+
if (stopping) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (counter.value() < desired()) {
|
|
26
|
+
void (async () => {
|
|
27
|
+
try {
|
|
28
|
+
counter.increment();
|
|
29
|
+
await fn();
|
|
30
|
+
} finally {
|
|
31
|
+
counter.decrement();
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handle();
|
|
38
|
+
|
|
39
|
+
return async function stop() {
|
|
40
|
+
stopping = true;
|
|
41
|
+
|
|
42
|
+
if (stopDefer) {
|
|
43
|
+
await stopDefer.promise;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
stopDefer = stopDefer || Defer();
|
|
48
|
+
|
|
49
|
+
if (counter.value() <= 0) {
|
|
50
|
+
stopDefer?.resolve();
|
|
51
|
+
} else {
|
|
52
|
+
const unsubscribe = counter.subscribe(() => {
|
|
53
|
+
if (counter.value() <= 0) {
|
|
54
|
+
stopDefer?.resolve();
|
|
55
|
+
unsubscribe();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await stopDefer.promise;
|
|
61
|
+
unsubscribeCounter();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type IConcurrencyResultResolver<Output> = () => Promise<Output>;
|
|
66
|
+
export type IPublishToConcurrency<Input, Output> = ((
|
|
67
|
+
message: Input
|
|
68
|
+
) => Promise<IConcurrencyResultResolver<Output>>) & {
|
|
69
|
+
finish: () => Promise<void>;
|
|
70
|
+
counter: ICounter; // concurrency counter
|
|
71
|
+
quantity: ICounter; // queued quantity
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function Concurrency<T, ReturnType = any>(
|
|
75
|
+
maxConcurrency: IValue<number>,
|
|
76
|
+
worker: (message: T) => Promise<ReturnType>
|
|
77
|
+
): IPublishToConcurrency<T, ReturnType> {
|
|
78
|
+
const max = Value<number>(maxConcurrency);
|
|
79
|
+
const concurrencyCounter = Counter();
|
|
80
|
+
const queuedQuantity = Counter();
|
|
81
|
+
|
|
82
|
+
let finishing = false;
|
|
83
|
+
|
|
84
|
+
function handle(message: T, defer: IDefer<any>) {
|
|
85
|
+
concurrencyCounter.increment();
|
|
86
|
+
|
|
87
|
+
void (async () => {
|
|
88
|
+
try {
|
|
89
|
+
defer.resolve(await worker(message));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
defer.reject(error);
|
|
92
|
+
} finally {
|
|
93
|
+
concurrencyCounter.decrement();
|
|
94
|
+
queuedQuantity.decrement();
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const canHandle = () => {
|
|
100
|
+
const maximum = max();
|
|
101
|
+
|
|
102
|
+
if (!maximum) {
|
|
103
|
+
return true;
|
|
104
|
+
} else {
|
|
105
|
+
return concurrencyCounter.value() < maximum;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
async function tryToHandle(message: T, defer: IDefer<any>) {
|
|
110
|
+
if (canHandle()) {
|
|
111
|
+
handle(message, defer);
|
|
112
|
+
} else {
|
|
113
|
+
await waitForCounter(concurrencyCounter, canHandle);
|
|
114
|
+
void tryToHandle(message, defer);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const publish = async (message: T) => {
|
|
119
|
+
// if (finishing) {
|
|
120
|
+
// throw new Error(`Finishing concurrency, publish is not allowed`);
|
|
121
|
+
// }
|
|
122
|
+
|
|
123
|
+
const defer = Defer<any>();
|
|
124
|
+
queuedQuantity.increment();
|
|
125
|
+
|
|
126
|
+
await tryToHandle(message, defer);
|
|
127
|
+
|
|
128
|
+
return async () => defer.promise;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
publish.counter = concurrencyCounter;
|
|
132
|
+
publish.quantity = queuedQuantity;
|
|
133
|
+
|
|
134
|
+
publish.finish = async () => {
|
|
135
|
+
finishing = true;
|
|
136
|
+
await waitForZeroCounter(concurrencyCounter);
|
|
137
|
+
concurrencyCounter.reset();
|
|
138
|
+
finishing = false;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return publish;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type IKeyType = string | number | undefined;
|
|
145
|
+
type IGetKey<T> = (message: T) => IKeyType | Promise<IKeyType>;
|
|
146
|
+
type IKeyedConcurrencyOptions = {
|
|
147
|
+
workerConcurrency: IValue<number>;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export function KeyedConcurrency<Input, Output = any>(
|
|
151
|
+
maxConcurrency: IValue<number>,
|
|
152
|
+
getKey: IGetKey<Input>,
|
|
153
|
+
worker: (message: Input) => Promise<Output>,
|
|
154
|
+
// options
|
|
155
|
+
{ workerConcurrency = 1 }: Partial<IKeyedConcurrencyOptions> = {}
|
|
156
|
+
): IPublishToConcurrency<Input, Output> {
|
|
157
|
+
const registry: {
|
|
158
|
+
[key in string | number]: IPublishToConcurrency<Input, Output>;
|
|
159
|
+
} = {};
|
|
160
|
+
|
|
161
|
+
return Concurrency<Input>(maxConcurrency, async (message: Input) => {
|
|
162
|
+
const key = await getKey(message);
|
|
163
|
+
|
|
164
|
+
if (key === undefined) {
|
|
165
|
+
return worker(message);
|
|
166
|
+
} else {
|
|
167
|
+
if (!registry[key]) {
|
|
168
|
+
const keyedControl = Concurrency<Input>(workerConcurrency, worker);
|
|
169
|
+
registry[key] = keyedControl;
|
|
170
|
+
|
|
171
|
+
const unsubscribe = keyedControl.quantity.subscribe(value => {
|
|
172
|
+
if (value <= 0) {
|
|
173
|
+
unsubscribe();
|
|
174
|
+
|
|
175
|
+
if (registry[key]) {
|
|
176
|
+
delete registry[key];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const resolver = await registry[key](message);
|
|
183
|
+
return await resolver();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const ContainerDefinitionSymbol = Symbol('ContainerDefinitionSymbol');
|
|
2
|
+
|
|
3
|
+
const TYPE_SERVICE = 'service';
|
|
4
|
+
const TYPE_FACTORY = 'factory';
|
|
5
|
+
|
|
6
|
+
export type IContainerServiceDefinition<Shape, ReturnType = void> = {
|
|
7
|
+
type: typeof TYPE_SERVICE;
|
|
8
|
+
factory: (box: Shape) => ReturnType;
|
|
9
|
+
[ContainerDefinitionSymbol]: true;
|
|
10
|
+
};
|
|
11
|
+
export type IContainerFactoryDefinition<Shape, ReturnType = void> = {
|
|
12
|
+
type: typeof TYPE_FACTORY;
|
|
13
|
+
factory: (box: Shape) => ReturnType;
|
|
14
|
+
[ContainerDefinitionSymbol]: true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type IContainerDefinition<Shape, ReturnType> =
|
|
18
|
+
| IContainerServiceDefinition<Shape, ReturnType>
|
|
19
|
+
| IContainerFactoryDefinition<Shape, ReturnType>;
|
|
20
|
+
|
|
21
|
+
export type IContainerSpecDefinition<Shape, ReturnType> =
|
|
22
|
+
| ReturnType
|
|
23
|
+
| IContainerDefinition<Shape, ReturnType>;
|
|
24
|
+
|
|
25
|
+
export type IContainerSpec<Shape extends object> = {
|
|
26
|
+
[P in keyof Shape]: IContainerSpecDefinition<Shape, Shape[P]>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function isContainerDefinition(definition: IContainerSpecDefinition<any, any>): boolean {
|
|
30
|
+
return definition instanceof Object && definition[ContainerDefinitionSymbol];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ContainerService<Shape = any, ReturnType = void>(
|
|
34
|
+
creator: (box: Shape) => ReturnType
|
|
35
|
+
): IContainerServiceDefinition<Shape, ReturnType> {
|
|
36
|
+
let called = false;
|
|
37
|
+
let cachedValue: any = undefined;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
type: TYPE_SERVICE,
|
|
41
|
+
|
|
42
|
+
factory: box => {
|
|
43
|
+
if (called) {
|
|
44
|
+
return cachedValue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
called = true;
|
|
48
|
+
cachedValue = creator(box);
|
|
49
|
+
|
|
50
|
+
return cachedValue;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
[ContainerDefinitionSymbol]: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function ContainerFactory<Shape = any, ReturnType = void>(
|
|
58
|
+
creator: (box: Shape) => ReturnType
|
|
59
|
+
): IContainerFactoryDefinition<Shape, ReturnType> {
|
|
60
|
+
return {
|
|
61
|
+
type: TYPE_FACTORY,
|
|
62
|
+
factory: creator,
|
|
63
|
+
[ContainerDefinitionSymbol]: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function Container<Shape extends object>(containerSpec: IContainerSpec<Shape>): Shape {
|
|
68
|
+
const box: any = new Proxy<IContainerSpec<Shape>>(containerSpec, {
|
|
69
|
+
get: (target, prop) => {
|
|
70
|
+
if (prop in target) {
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
const value: any = target[prop];
|
|
73
|
+
|
|
74
|
+
if (isContainerDefinition(value)) {
|
|
75
|
+
return (value as IContainerDefinition<any, any>).factory(box);
|
|
76
|
+
} else {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
throw new Error(`No value: "${String(prop)}"`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return box as Shape;
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IPubSubSubscribe, SyncPubSub } from './pubsub';
|
|
2
|
+
|
|
3
|
+
export type ICounter = {
|
|
4
|
+
increment: (n?: number) => number;
|
|
5
|
+
decrement: (n?: number) => number;
|
|
6
|
+
value: () => number;
|
|
7
|
+
subscribe: IPubSubSubscribe<number>;
|
|
8
|
+
reset: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Counter(): ICounter {
|
|
12
|
+
const topic = SyncPubSub<number>();
|
|
13
|
+
const { subscribe, publish } = topic;
|
|
14
|
+
|
|
15
|
+
let counter = 0;
|
|
16
|
+
|
|
17
|
+
function increment(n = 1) {
|
|
18
|
+
counter += n;
|
|
19
|
+
publish(counter);
|
|
20
|
+
return counter;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function decrement(n = 1) {
|
|
24
|
+
counter -= n;
|
|
25
|
+
publish(counter);
|
|
26
|
+
return counter;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function value() {
|
|
30
|
+
return counter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function reset() {
|
|
34
|
+
counter = 0;
|
|
35
|
+
publish(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
increment,
|
|
40
|
+
decrement,
|
|
41
|
+
value,
|
|
42
|
+
subscribe,
|
|
43
|
+
reset,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type IDefer<ResultType = void> = {
|
|
2
|
+
promise: Promise<ResultType>;
|
|
3
|
+
resolved: boolean;
|
|
4
|
+
rejected: boolean;
|
|
5
|
+
pending: boolean;
|
|
6
|
+
resolve(value: ResultType | PromiseLike<ResultType>): void;
|
|
7
|
+
reject(reason?: unknown): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function Defer<ResultType = void>(): IDefer<ResultType> {
|
|
11
|
+
const deferred = {
|
|
12
|
+
rejected: false,
|
|
13
|
+
resolved: false,
|
|
14
|
+
pending: true,
|
|
15
|
+
} as IDefer<ResultType>;
|
|
16
|
+
|
|
17
|
+
deferred.promise = new Promise<ResultType>((resolve, reject) => {
|
|
18
|
+
deferred.resolve = (data: any) => {
|
|
19
|
+
if (deferred.resolved || deferred.rejected) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deferred.pending = false;
|
|
24
|
+
deferred.resolved = true;
|
|
25
|
+
resolve(data);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
deferred.reject = (error: any) => {
|
|
29
|
+
if (deferred.resolved || deferred.rejected) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
deferred.pending = false;
|
|
34
|
+
deferred.rejected = true;
|
|
35
|
+
reject(error);
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return deferred;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type IDataDefer<DataType = any, ResultType = void> = IDefer<ResultType> & {
|
|
43
|
+
data: DataType;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function DeferData<DataType = void, ResultType = void>(
|
|
47
|
+
data: DataType
|
|
48
|
+
): IDataDefer<DataType, ResultType> {
|
|
49
|
+
const deferred = Defer<ResultType>();
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...deferred,
|
|
53
|
+
data,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function discriminate<K extends PropertyKey, V extends string | number | boolean>(
|
|
2
|
+
discriminantKey: K,
|
|
3
|
+
discriminantValue: V | V[]
|
|
4
|
+
) {
|
|
5
|
+
return <T extends Record<K, any>>(
|
|
6
|
+
obj: T & Record<K, V extends T[K] ? T[K] : V>
|
|
7
|
+
): obj is Extract<T, Record<K, V>> =>
|
|
8
|
+
Array.isArray(discriminantValue)
|
|
9
|
+
? discriminantValue.includes(obj[discriminantKey])
|
|
10
|
+
: obj[discriminantKey] === discriminantValue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function discriminateAndFilter<
|
|
14
|
+
T extends Record<K, any>,
|
|
15
|
+
K extends PropertyKey,
|
|
16
|
+
V extends string | number | boolean,
|
|
17
|
+
>(
|
|
18
|
+
discriminantKey: K,
|
|
19
|
+
discriminantValue: V | V[],
|
|
20
|
+
predicate: (item: Extract<T, Record<K, V>>) => boolean
|
|
21
|
+
) {
|
|
22
|
+
return (obj: T & Record<K, V extends T[K] ? T[K] : V>): obj is Extract<T, Record<K, V>> =>
|
|
23
|
+
(Array.isArray(discriminantValue)
|
|
24
|
+
? discriminantValue.includes(obj[discriminantKey])
|
|
25
|
+
: obj[discriminantKey] === discriminantValue) && predicate(obj as any);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type A = { type: 'a'; a: string };
|
|
29
|
+
type B = { type: 'b'; b: string };
|
|
30
|
+
type X = A | B;
|
|
31
|
+
|
|
32
|
+
function test(x: X[]) {
|
|
33
|
+
const result = x.find(discriminateAndFilter('type', 'b', z => z.b === 'toto'));
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const getEnumValues = <T>(enumType: Record<string, T>) => {
|
|
2
|
+
const values = Object.values(enumType);
|
|
3
|
+
if (values.some(isNumber)) {
|
|
4
|
+
// Number-based enum
|
|
5
|
+
return values.filter(isNumber);
|
|
6
|
+
} else {
|
|
7
|
+
// String-based enum
|
|
8
|
+
return values;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isNumber = (value: any) => !isNaN(Number(value));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { assert } from 'console';
|
|
2
|
+
|
|
3
|
+
// From https://www.tylercrosse.com/ideas/exponential-backoff/
|
|
4
|
+
export function exponentialBackoff({
|
|
5
|
+
attempt,
|
|
6
|
+
baseDelay = 100,
|
|
7
|
+
maxDelay,
|
|
8
|
+
}: {
|
|
9
|
+
attempt: number;
|
|
10
|
+
baseDelay: number;
|
|
11
|
+
maxDelay?: number;
|
|
12
|
+
}) {
|
|
13
|
+
assert(attempt >= 1, `attempt must be greater than or equal to 1`);
|
|
14
|
+
assert(baseDelay >= 1, `baseDelay must be greater than or equal to 1`);
|
|
15
|
+
assert(maxDelay === undefined || maxDelay >= 1, `baseDelay must be greater than or equal to 1`);
|
|
16
|
+
|
|
17
|
+
const exponential = Math.pow(2, attempt) * baseDelay;
|
|
18
|
+
const delay = maxDelay ? Math.min(exponential, maxDelay) : exponential;
|
|
19
|
+
return Math.floor(Math.random() * delay);
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isIterable } from './isIterable';
|
|
2
|
+
|
|
3
|
+
export function flatten<T>(items: T | T[] | Iterable<T>): T[] {
|
|
4
|
+
if (Array.isArray(items) || isIterable(items)) {
|
|
5
|
+
return [...items].reduce((acc, item) => {
|
|
6
|
+
return [...acc, ...flatten(item)];
|
|
7
|
+
}, [] as T[]);
|
|
8
|
+
} else {
|
|
9
|
+
return [items];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// TODO @@@@slava tests
|
|
2
|
+
export function hashCodeNative(input: string): number {
|
|
3
|
+
let hash = 0;
|
|
4
|
+
let i;
|
|
5
|
+
let charCode;
|
|
6
|
+
|
|
7
|
+
for (i = 0; i < input.length; i++) {
|
|
8
|
+
charCode = input.charCodeAt(i);
|
|
9
|
+
hash = (hash << 5) - hash + charCode;
|
|
10
|
+
hash |= 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return hash;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function hashCode(input: string): number {
|
|
17
|
+
return Math.abs(hashCodeNative(input));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hashHex(value: string, bits = 64): string {
|
|
21
|
+
const radix = 16; // hex
|
|
22
|
+
const size = Math.ceil(bits / 8) * 2;
|
|
23
|
+
|
|
24
|
+
let result = '';
|
|
25
|
+
let idx = 0;
|
|
26
|
+
let prevHash = '';
|
|
27
|
+
|
|
28
|
+
while (result.length < size) {
|
|
29
|
+
prevHash = hashCode(`${idx++}${prevHash}${value}`).toString(radix);
|
|
30
|
+
result += prevHash;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result.slice(0, size);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function HashBalance(hash: (input: string) => number = hashCode) {
|
|
37
|
+
return function balance(value: string, members: number): number {
|
|
38
|
+
if (members <= 0) {
|
|
39
|
+
return 0;
|
|
40
|
+
} else {
|
|
41
|
+
return hash(value) % members;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hexToNumber(hex: string): number {
|
|
47
|
+
if (hex.length % 2 != 0) {
|
|
48
|
+
hex = '0' + hex;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let num = parseInt(hex, 16);
|
|
52
|
+
const maxVal = Math.pow(2, (hex.length / 2) * 8);
|
|
53
|
+
|
|
54
|
+
if (num > maxVal / 2 - 1) {
|
|
55
|
+
num = num - maxVal;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return num;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function HashToNumber(Hash: (value: string) => Promise<string> = async v => hashHex(v, 32)) {
|
|
62
|
+
return async (value: string) => {
|
|
63
|
+
const hex8bytes = (await Hash(value)).slice(0, 8);
|
|
64
|
+
return hexToNumber(hex8bytes);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { IPubSub, IPubSubSubscriber, IPubSubUnsubscribe, PubSub } from './pubsub';
|
|
2
|
+
|
|
3
|
+
export type IHubTopicName = string | any;
|
|
4
|
+
|
|
5
|
+
export type IHubUnsubscribe = IPubSubUnsubscribe;
|
|
6
|
+
|
|
7
|
+
export type IHubSubscriber<DataType = any> = IPubSubSubscriber<DataType>;
|
|
8
|
+
|
|
9
|
+
export type IHub = {
|
|
10
|
+
subscribe: <DataType = any>(
|
|
11
|
+
topic: IHubTopicName,
|
|
12
|
+
subscriber: IHubSubscriber<DataType>
|
|
13
|
+
) => IHubUnsubscribe;
|
|
14
|
+
|
|
15
|
+
publish: <DataType = any>(topic: IHubTopicName, data: DataType) => void | Promise<void>;
|
|
16
|
+
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function Hub(): IHub {
|
|
21
|
+
let topics: { [topic in IHubTopicName]: IPubSub<any> } = {};
|
|
22
|
+
|
|
23
|
+
let unSubscribers: IHubUnsubscribe[] = [];
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
publish<DataType = any>(topic: IHubTopicName, data: DataType): Promise<void> | void {
|
|
27
|
+
topics[topic] = topics[topic] || PubSub();
|
|
28
|
+
return topics[topic].publish(data);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
close(): void {
|
|
32
|
+
unSubscribers.forEach(un => un());
|
|
33
|
+
unSubscribers = [];
|
|
34
|
+
topics = {};
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
subscribe<DataType = any>(
|
|
38
|
+
topic: IHubTopicName,
|
|
39
|
+
subscriber: IHubSubscriber<DataType>
|
|
40
|
+
): IHubUnsubscribe {
|
|
41
|
+
topics[topic] = topics[topic] || PubSub();
|
|
42
|
+
|
|
43
|
+
const unsubscribeTopic = topics[topic].subscribe(subscriber);
|
|
44
|
+
|
|
45
|
+
unSubscribers.push(unsubscribeTopic);
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
unsubscribeTopic();
|
|
49
|
+
unSubscribers = unSubscribers.filter(un => un !== unsubscribeTopic);
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
|
|
3
|
+
export const generateRandomId = () => uuid().toString();
|
|
4
|
+
|
|
5
|
+
export const generateSafeRandomId = () => uuid().toString().replace(/-/g, '');
|
|
6
|
+
|
|
7
|
+
const UUID_PATTERN =
|
|
8
|
+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
9
|
+
|
|
10
|
+
export const checkFormatOfUUID = (value: string): boolean => UUID_PATTERN.test(value);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Defer, IDefer } from './defer';
|
|
2
|
+
|
|
3
|
+
export type IFinishInterval = () => Promise<void>;
|
|
4
|
+
|
|
5
|
+
export function Interval(
|
|
6
|
+
fn: () => any | Promise<any>,
|
|
7
|
+
timeWindow: number,
|
|
8
|
+
startImmediately = false
|
|
9
|
+
): IFinishInterval {
|
|
10
|
+
let finishing = false;
|
|
11
|
+
let finished = false;
|
|
12
|
+
let finishingDefer: IDefer<void> | undefined = undefined;
|
|
13
|
+
let delayDefer: IDefer<void> = Defer<void>();
|
|
14
|
+
let timer: any;
|
|
15
|
+
|
|
16
|
+
function clearTimerIfExists() {
|
|
17
|
+
if (timer) {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function execute() {
|
|
24
|
+
if (!finishing) {
|
|
25
|
+
await fn();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (finishingDefer) {
|
|
29
|
+
finishingDefer.resolve();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clearTimerIfExists();
|
|
33
|
+
|
|
34
|
+
if (finishing) {
|
|
35
|
+
finished = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void (async () => {
|
|
40
|
+
if (startImmediately) {
|
|
41
|
+
await execute();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
while (!finishing) {
|
|
45
|
+
// delay
|
|
46
|
+
delayDefer.resolve();
|
|
47
|
+
delayDefer = Defer<void>();
|
|
48
|
+
|
|
49
|
+
timer = setTimeout(() => delayDefer.resolve(), timeWindow);
|
|
50
|
+
await delayDefer.promise;
|
|
51
|
+
|
|
52
|
+
// execution
|
|
53
|
+
await execute();
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
|
|
57
|
+
return async () => {
|
|
58
|
+
finishing = true;
|
|
59
|
+
|
|
60
|
+
if (!finishingDefer) {
|
|
61
|
+
finishingDefer = Defer<void>();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (finished) {
|
|
65
|
+
finishingDefer.resolve();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (delayDefer) {
|
|
69
|
+
delayDefer.resolve();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
clearTimerIfExists();
|
|
73
|
+
|
|
74
|
+
await finishingDefer.promise;
|
|
75
|
+
};
|
|
76
|
+
}
|