mlclaw 0.1.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/.agents/skills/mlclaw/SKILL.md +214 -0
- package/.agents/skills/mlclaw/agents/openai.yaml +4 -0
- package/.gitattributes +35 -0
- package/Dockerfile +45 -0
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/assets/mlclaw.svg +143 -0
- package/dist/hf-state-sync.js +9532 -0
- package/dist/mlclaw-space-runtime.js +5010 -0
- package/dist/mlclaw.mjs +16502 -0
- package/entrypoint.sh +87 -0
- package/mlclaw.ps1 +108 -0
- package/mlclaw.sh +117 -0
- package/openclaw.default.json +67 -0
- package/package.json +66 -0
- package/scripts/configure-huggingface-model.mjs +86 -0
- package/scripts/configure-telegram.mjs +55 -0
- package/scripts/report-telegram-probe.mjs +18 -0
- package/space/README.md +42 -0
- package/src/hf-bucket-client/client.ts +217 -0
- package/src/hf-state-sync/archive.ts +137 -0
- package/src/hf-state-sync/cli.ts +92 -0
- package/src/hf-state-sync/hub.ts +81 -0
- package/src/hf-state-sync/manifest.ts +67 -0
- package/src/hf-state-sync/paths.ts +73 -0
- package/src/hf-state-sync/restore.ts +109 -0
- package/src/hf-state-sync/snapshot.ts +133 -0
- package/src/hf-state-sync/sqlite.ts +57 -0
- package/src/hf-state-sync/supervise.ts +256 -0
- package/src/vendor/hfjs-xet/error.ts +52 -0
- package/src/vendor/hfjs-xet/types/public.ts +207 -0
- package/src/vendor/hfjs-xet/utils/ChunkCache.ts +102 -0
- package/src/vendor/hfjs-xet/utils/RangeList.ts +182 -0
- package/src/vendor/hfjs-xet/utils/SplicedBlob.ts +249 -0
- package/src/vendor/hfjs-xet/utils/XetBlob.ts +732 -0
- package/src/vendor/hfjs-xet/utils/checkCredentials.ts +21 -0
- package/src/vendor/hfjs-xet/utils/combineUint8Arrays.ts +13 -0
- package/src/vendor/hfjs-xet/utils/createXorbs.ts +782 -0
- package/src/vendor/hfjs-xet/utils/shardParser.ts +152 -0
- package/src/vendor/hfjs-xet/utils/sum.ts +9 -0
- package/src/vendor/hfjs-xet/utils/uploadShards.ts +443 -0
- package/src/vendor/hfjs-xet/utils/xetWriteToken.ts +101 -0
- package/src/vendor/hfjs-xet/vendor/lz4js/index.ts +540 -0
- package/src/vendor/hfjs-xet/vendor/lz4js/util.ts +57 -0
- package/src/vendor/hfjs-xet/vendor/lz4js/xxh32.ts +99 -0
- package/src/vendor/hfjs-xet/vendor/type-fest/basic.ts +34 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// @ts-nocheck -- vendored upstream code, kept verbatim; our strict tsconfig does not apply.
|
|
2
|
+
// Vendored from huggingface/huggingface.js@f8fdf6be (packages/hub/src/types/public.ts), MIT License.
|
|
3
|
+
// Delete this directory when bucket support is upstreamed to @huggingface/hub.
|
|
4
|
+
import type { PipelineType } from "@huggingface/tasks";
|
|
5
|
+
|
|
6
|
+
export type RepoType = "space" | "dataset" | "model" | "bucket" | "kernel";
|
|
7
|
+
|
|
8
|
+
export interface RepoId {
|
|
9
|
+
name: string;
|
|
10
|
+
type: RepoType;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type RepoFullName =
|
|
14
|
+
| string
|
|
15
|
+
| `spaces/${string}`
|
|
16
|
+
| `datasets/${string}`
|
|
17
|
+
| `buckets/${string}`
|
|
18
|
+
| `kernels/${string}`;
|
|
19
|
+
|
|
20
|
+
export type RepoDesignation = RepoId | RepoFullName;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A {@link RepoDesignation} narrowed to bucket repos.
|
|
24
|
+
*
|
|
25
|
+
* Used by APIs that only operate on buckets (e.g. {@link copyFile}, {@link copyFiles},
|
|
26
|
+
* {@link copyFolder}).
|
|
27
|
+
*/
|
|
28
|
+
export type BucketDesignation = { type: "bucket"; name: string } | `buckets/${string}`;
|
|
29
|
+
|
|
30
|
+
/** Actually `hf_${string}`, but for convenience, using the string type */
|
|
31
|
+
export type AccessToken = string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Use `AccessToken` instead. Pass { accessToken: "hf_..." } instead of { credentials: { accessToken: "hf_..." } }
|
|
35
|
+
*/
|
|
36
|
+
export interface Credentials {
|
|
37
|
+
accessToken: AccessToken;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type CredentialsParams =
|
|
41
|
+
| {
|
|
42
|
+
accessToken?: undefined;
|
|
43
|
+
/**
|
|
44
|
+
* @deprecated Use `accessToken` instead
|
|
45
|
+
*/
|
|
46
|
+
credentials: Credentials;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
accessToken: AccessToken;
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated Use `accessToken` instead
|
|
52
|
+
*/
|
|
53
|
+
credentials?: undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type SpaceHardwareFlavor =
|
|
57
|
+
| "cpu-basic"
|
|
58
|
+
| "cpu-upgrade"
|
|
59
|
+
| "cpu-performance"
|
|
60
|
+
| "cpu-xl"
|
|
61
|
+
| "sprx8"
|
|
62
|
+
| "zero-a10g"
|
|
63
|
+
| "inf2x6"
|
|
64
|
+
| "t4-small"
|
|
65
|
+
| "t4-medium"
|
|
66
|
+
| "l4x1"
|
|
67
|
+
| "l4x4"
|
|
68
|
+
| "l40sx1"
|
|
69
|
+
| "l40sx4"
|
|
70
|
+
| "l40sx8"
|
|
71
|
+
| "a10g-small"
|
|
72
|
+
| "a10g-large"
|
|
73
|
+
| "a10g-largex2"
|
|
74
|
+
| "a10g-largex4"
|
|
75
|
+
| "a100-large"
|
|
76
|
+
| "a100x4"
|
|
77
|
+
| "a100x8";
|
|
78
|
+
|
|
79
|
+
export type SpaceSdk = "streamlit" | "gradio" | "docker" | "static";
|
|
80
|
+
|
|
81
|
+
export type SpaceStage =
|
|
82
|
+
| "NO_APP_FILE"
|
|
83
|
+
| "CONFIG_ERROR"
|
|
84
|
+
| "BUILDING"
|
|
85
|
+
| "BUILD_ERROR"
|
|
86
|
+
| "RUNNING"
|
|
87
|
+
| "RUNNING_BUILDING"
|
|
88
|
+
| "RUNTIME_ERROR"
|
|
89
|
+
| "DELETING"
|
|
90
|
+
| "PAUSED"
|
|
91
|
+
| "SLEEPING";
|
|
92
|
+
|
|
93
|
+
export type AccessTokenRole = "admin" | "write" | "contributor" | "read";
|
|
94
|
+
|
|
95
|
+
export type AuthType = "access_token" | "app_token" | "app_token_as_user";
|
|
96
|
+
|
|
97
|
+
export type { PipelineType };
|
|
98
|
+
|
|
99
|
+
export interface SpaceRuntime {
|
|
100
|
+
stage: SpaceStage;
|
|
101
|
+
sdk?: SpaceSdk;
|
|
102
|
+
sdkVersion?: string;
|
|
103
|
+
errorMessage?: string;
|
|
104
|
+
hardware?: {
|
|
105
|
+
current: SpaceHardwareFlavor | null;
|
|
106
|
+
currentPrettyName?: string;
|
|
107
|
+
requested: SpaceHardwareFlavor | null;
|
|
108
|
+
requestedPrettyName?: string;
|
|
109
|
+
};
|
|
110
|
+
/** when calling /spaces, those props are only fetched if ?full=true */
|
|
111
|
+
resources?: SpaceResourceConfig;
|
|
112
|
+
/** in seconds */
|
|
113
|
+
gcTimeout?: number | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SpaceResourceRequirement {
|
|
117
|
+
cpu?: string;
|
|
118
|
+
memory?: string;
|
|
119
|
+
gpu?: string;
|
|
120
|
+
gpuModel?: string;
|
|
121
|
+
ephemeral?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface SpaceResourceConfig {
|
|
125
|
+
requests: SpaceResourceRequirement;
|
|
126
|
+
limits: SpaceResourceRequirement;
|
|
127
|
+
replicas?: number;
|
|
128
|
+
throttled?: boolean;
|
|
129
|
+
is_custom?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type License =
|
|
133
|
+
| "apache-2.0"
|
|
134
|
+
| "mit"
|
|
135
|
+
| "openrail"
|
|
136
|
+
| "bigscience-openrail-m"
|
|
137
|
+
| "creativeml-openrail-m"
|
|
138
|
+
| "bigscience-bloom-rail-1.0"
|
|
139
|
+
| "bigcode-openrail-m"
|
|
140
|
+
| "afl-3.0"
|
|
141
|
+
| "artistic-2.0"
|
|
142
|
+
| "bsl-1.0"
|
|
143
|
+
| "bsd"
|
|
144
|
+
| "bsd-2-clause"
|
|
145
|
+
| "bsd-3-clause"
|
|
146
|
+
| "bsd-3-clause-clear"
|
|
147
|
+
| "c-uda"
|
|
148
|
+
| "cc"
|
|
149
|
+
| "cc0-1.0"
|
|
150
|
+
| "cc-by-2.0"
|
|
151
|
+
| "cc-by-2.5"
|
|
152
|
+
| "cc-by-3.0"
|
|
153
|
+
| "cc-by-4.0"
|
|
154
|
+
| "cc-by-sa-3.0"
|
|
155
|
+
| "cc-by-sa-4.0"
|
|
156
|
+
| "cc-by-nc-2.0"
|
|
157
|
+
| "cc-by-nc-3.0"
|
|
158
|
+
| "cc-by-nc-4.0"
|
|
159
|
+
| "cc-by-nd-4.0"
|
|
160
|
+
| "cc-by-nc-nd-3.0"
|
|
161
|
+
| "cc-by-nc-nd-4.0"
|
|
162
|
+
| "cc-by-nc-sa-2.0"
|
|
163
|
+
| "cc-by-nc-sa-3.0"
|
|
164
|
+
| "cc-by-nc-sa-4.0"
|
|
165
|
+
| "cdla-sharing-1.0"
|
|
166
|
+
| "cdla-permissive-1.0"
|
|
167
|
+
| "cdla-permissive-2.0"
|
|
168
|
+
| "wtfpl"
|
|
169
|
+
| "ecl-2.0"
|
|
170
|
+
| "epl-1.0"
|
|
171
|
+
| "epl-2.0"
|
|
172
|
+
| "etalab-2.0"
|
|
173
|
+
| "eupl-1.1"
|
|
174
|
+
| "agpl-3.0"
|
|
175
|
+
| "gfdl"
|
|
176
|
+
| "gpl"
|
|
177
|
+
| "gpl-2.0"
|
|
178
|
+
| "gpl-3.0"
|
|
179
|
+
| "lgpl"
|
|
180
|
+
| "lgpl-2.1"
|
|
181
|
+
| "lgpl-3.0"
|
|
182
|
+
| "isc"
|
|
183
|
+
| "lppl-1.3c"
|
|
184
|
+
| "ms-pl"
|
|
185
|
+
| "mpl-2.0"
|
|
186
|
+
| "odc-by"
|
|
187
|
+
| "odbl"
|
|
188
|
+
| "openrail++"
|
|
189
|
+
| "osl-3.0"
|
|
190
|
+
| "postgresql"
|
|
191
|
+
| "ofl-1.1"
|
|
192
|
+
| "ncsa"
|
|
193
|
+
| "unlicense"
|
|
194
|
+
| "zlib"
|
|
195
|
+
| "pddl"
|
|
196
|
+
| "lgpl-lr"
|
|
197
|
+
| "deepfloyd-if-license"
|
|
198
|
+
| "llama2"
|
|
199
|
+
| "llama3"
|
|
200
|
+
| "llama3.1"
|
|
201
|
+
| "llama3.2"
|
|
202
|
+
| "llama3.3"
|
|
203
|
+
| "gemma"
|
|
204
|
+
| "apple-ascl"
|
|
205
|
+
| "apple-amlr"
|
|
206
|
+
| "unknown"
|
|
207
|
+
| "other";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @ts-nocheck -- vendored upstream code, kept verbatim; our strict tsconfig does not apply.
|
|
2
|
+
// Vendored from huggingface/huggingface.js@f8fdf6be (packages/hub/src/utils/ChunkCache.ts), MIT License.
|
|
3
|
+
// Delete this directory when bucket support is upstreamed to @huggingface/hub.
|
|
4
|
+
const CHUNK_CACHE_INITIAL_SIZE = 10_000;
|
|
5
|
+
const CHUNK_CACHE_GROW_FACTOR = 1.5;
|
|
6
|
+
const CHUNK_CACHE_MAX_SIZE = 1_000_000;
|
|
7
|
+
|
|
8
|
+
export class ChunkCache {
|
|
9
|
+
index = 0;
|
|
10
|
+
// Index >= 0 means local xorb, < 0 means remote xorb
|
|
11
|
+
xorbIndices: Int32Array;
|
|
12
|
+
// Max 8K chunks per xorb, less than 64K uint16_t
|
|
13
|
+
chunkIndices: Uint16Array;
|
|
14
|
+
map = new Map<string, number>(); // hash -> chunkCacheIndex. Less overhead that way, empty object is 60+B and empty array is 40+B
|
|
15
|
+
hmacs = new Set<string>(); // todo : remove old hmacs
|
|
16
|
+
maxSize: number;
|
|
17
|
+
|
|
18
|
+
constructor(maxSize: number = CHUNK_CACHE_MAX_SIZE) {
|
|
19
|
+
if (maxSize < 1) {
|
|
20
|
+
throw new Error("maxSize must be at least 1");
|
|
21
|
+
}
|
|
22
|
+
this.maxSize = maxSize;
|
|
23
|
+
this.xorbIndices = new Int32Array(Math.min(CHUNK_CACHE_INITIAL_SIZE, maxSize));
|
|
24
|
+
this.chunkIndices = new Uint16Array(Math.min(CHUNK_CACHE_INITIAL_SIZE, maxSize));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addChunkToCache(hash: string, xorbIndex: number, chunkIndex: number, hmac: string | null): void {
|
|
28
|
+
if (this.map.has(hash)) {
|
|
29
|
+
// Happens when we receive an existing chunk from remote dedup info (eg duplicate chunk in shard? Or shards with same hmac key
|
|
30
|
+
// sharing chunks/xorbs)
|
|
31
|
+
|
|
32
|
+
// processing this chunk again would desync the cache, as `this.map.size` would not increase, as opposed to `this.index`
|
|
33
|
+
|
|
34
|
+
// We could readd/remove it to "refresh it"
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this.map.values().next().value === this.index) {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
39
|
+
this.map.delete(this.map.keys().next().value!);
|
|
40
|
+
}
|
|
41
|
+
this.map.set(hash, this.index);
|
|
42
|
+
if (hmac !== null) {
|
|
43
|
+
this.hmacs.add(hmac);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.index >= this.xorbIndices.length) {
|
|
47
|
+
// todo: switch to resize() with modern browsers
|
|
48
|
+
const oldXorbIndices = this.xorbIndices;
|
|
49
|
+
const oldChunkIndices = this.chunkIndices;
|
|
50
|
+
this.xorbIndices = new Int32Array(Math.min(this.xorbIndices.length * CHUNK_CACHE_GROW_FACTOR, this.maxSize));
|
|
51
|
+
this.chunkIndices = new Uint16Array(Math.min(this.chunkIndices.length * CHUNK_CACHE_GROW_FACTOR, this.maxSize));
|
|
52
|
+
this.xorbIndices.set(oldXorbIndices);
|
|
53
|
+
this.chunkIndices.set(oldChunkIndices);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.xorbIndices[this.index] = xorbIndex;
|
|
57
|
+
this.chunkIndices[this.index] = chunkIndex;
|
|
58
|
+
this.index = (this.index + 1) % this.maxSize;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getChunk(
|
|
62
|
+
hash: string,
|
|
63
|
+
/**
|
|
64
|
+
* Set to null if you only want to check against locally created chunks, or the hash is already a hmac
|
|
65
|
+
*/
|
|
66
|
+
hmacFunction: ((hash: string, key: string) => string) | null,
|
|
67
|
+
):
|
|
68
|
+
| {
|
|
69
|
+
xorbIndex: number;
|
|
70
|
+
chunkIndex: number;
|
|
71
|
+
}
|
|
72
|
+
| undefined {
|
|
73
|
+
let index = this.map.get(hash);
|
|
74
|
+
if (index === undefined && hmacFunction !== null) {
|
|
75
|
+
for (const hmac of this.hmacs) {
|
|
76
|
+
index = this.map.get(hmacFunction(hash, hmac));
|
|
77
|
+
if (index !== undefined) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (index === undefined) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
xorbIndex: this.xorbIndices[index],
|
|
87
|
+
chunkIndex: this.chunkIndices[index],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
updateChunkIndex(hash: string, chunkIndex: number): void {
|
|
92
|
+
const index = this.map.get(hash);
|
|
93
|
+
if (index === undefined) {
|
|
94
|
+
throw new Error(`Chunk not found in cache: ${hash}`);
|
|
95
|
+
}
|
|
96
|
+
this.chunkIndices[index] = chunkIndex;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
removeChunkFromCache(hash: string): void {
|
|
100
|
+
this.map.delete(hash);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// @ts-nocheck -- vendored upstream code, kept verbatim; our strict tsconfig does not apply.
|
|
2
|
+
// Vendored from huggingface/huggingface.js@f8fdf6be (packages/hub/src/utils/RangeList.ts), MIT License.
|
|
3
|
+
// Delete this directory when bucket support is upstreamed to @huggingface/hub.
|
|
4
|
+
/**
|
|
5
|
+
* Code generated with this prompt by Cursor:
|
|
6
|
+
*
|
|
7
|
+
* I want to build a class to manage ranges
|
|
8
|
+
*
|
|
9
|
+
* I can add ranges to it with a start& an end (both integer, end > start). It should store those ranges efficiently.
|
|
10
|
+
*
|
|
11
|
+
* When several ranges overlap, eg [1, 100] and [30, 50], I want the class to split the range into non-overlapping ranges, and add a "ref counter" to the ranges. For example, [1, 30], [30, 50] * 2, [50, 100]
|
|
12
|
+
*
|
|
13
|
+
* I also want to be able to remove ranges, it will decrease the ref counter or remove the range altogether. I can only remove ranges at existing boundaries. For example, with the [1, 30], [30, 50] * 2, [50, 100] configuration
|
|
14
|
+
*
|
|
15
|
+
* - removing [1, 100] => the only range remaning is [30, 50]
|
|
16
|
+
* - removing [2, 50] => error, because "2' is not a boundary
|
|
17
|
+
* - removing [30, 50] => [1, 30], [30, 50], [50, 100] (do not "merge" the ranges back together)
|
|
18
|
+
*
|
|
19
|
+
* I want to be able to associate data to each range. And I want to be able to get the ranges inside boundaries. For example , with [1, 30], [30, 50] * 2, [50, 100] configuration
|
|
20
|
+
*
|
|
21
|
+
* - getting [30, 100] => I receive [30, 50] * 2, [50, 100], and I can get / modify the data associated to each range by accessing their data prop. Note the "*2" is just the ref counter, there is onlly one range object for the interval returned
|
|
22
|
+
* - getting [2, 50] => I get [30, 50] * 2
|
|
23
|
+
*
|
|
24
|
+
* ----
|
|
25
|
+
*
|
|
26
|
+
* Could optimize with binary search, but the ranges we want to handle are not that many.
|
|
27
|
+
*/
|
|
28
|
+
interface Range<T> {
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
refCount: number;
|
|
32
|
+
data: T | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class RangeList<T> {
|
|
36
|
+
private ranges: Range<T>[] = [];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Add a range to the list. If it overlaps with existing ranges,
|
|
40
|
+
* it will split them and increment reference counts accordingly.
|
|
41
|
+
*/
|
|
42
|
+
add(start: number, end: number): void {
|
|
43
|
+
if (end <= start) {
|
|
44
|
+
throw new TypeError("End must be greater than start");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Find all ranges that overlap with the new range
|
|
48
|
+
const overlappingRanges: { index: number; range: Range<T> }[] = [];
|
|
49
|
+
for (let i = 0; i < this.ranges.length; i++) {
|
|
50
|
+
const range = this.ranges[i];
|
|
51
|
+
if (start < range.end && end > range.start) {
|
|
52
|
+
overlappingRanges.push({ index: i, range });
|
|
53
|
+
}
|
|
54
|
+
if (range.data !== null) {
|
|
55
|
+
throw new Error("Overlapping range already has data");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (overlappingRanges.length === 0) {
|
|
60
|
+
// No overlaps, just add the new range
|
|
61
|
+
this.ranges.push({ start, end, refCount: 1, data: null });
|
|
62
|
+
this.ranges.sort((a, b) => a.start - b.start);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Handle overlaps by splitting ranges
|
|
67
|
+
const newRanges: Range<T>[] = [];
|
|
68
|
+
let currentPos = start;
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < overlappingRanges.length; i++) {
|
|
71
|
+
const { range } = overlappingRanges[i];
|
|
72
|
+
|
|
73
|
+
// Add range before overlap if exists
|
|
74
|
+
if (currentPos < range.start) {
|
|
75
|
+
newRanges.push({
|
|
76
|
+
start: currentPos,
|
|
77
|
+
end: range.start,
|
|
78
|
+
refCount: 1,
|
|
79
|
+
data: null,
|
|
80
|
+
});
|
|
81
|
+
} else if (range.start < currentPos) {
|
|
82
|
+
newRanges.push({
|
|
83
|
+
start: range.start,
|
|
84
|
+
end: currentPos,
|
|
85
|
+
refCount: range.refCount,
|
|
86
|
+
data: null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add overlapping part with increased ref count
|
|
91
|
+
newRanges.push({
|
|
92
|
+
start: Math.max(currentPos, range.start),
|
|
93
|
+
end: Math.min(end, range.end),
|
|
94
|
+
refCount: range.refCount + 1,
|
|
95
|
+
data: null,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Add remaining part of existing range if exists
|
|
99
|
+
if (range.end > end) {
|
|
100
|
+
newRanges.push({
|
|
101
|
+
start: end,
|
|
102
|
+
end: range.end,
|
|
103
|
+
refCount: range.refCount,
|
|
104
|
+
data: null,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
currentPos = Math.max(currentPos, range.end);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add remaining part after last overlap if exists
|
|
112
|
+
if (currentPos < end) {
|
|
113
|
+
newRanges.push({
|
|
114
|
+
start: currentPos,
|
|
115
|
+
end,
|
|
116
|
+
refCount: 1,
|
|
117
|
+
data: null,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Remove old overlapping ranges and insert new ones
|
|
122
|
+
const firstIndex = overlappingRanges[0].index;
|
|
123
|
+
const lastIndex = overlappingRanges[overlappingRanges.length - 1].index;
|
|
124
|
+
this.ranges.splice(firstIndex, lastIndex - firstIndex + 1, ...newRanges);
|
|
125
|
+
this.ranges.sort((a, b) => a.start - b.start);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Remove a range from the list. The range must start and end at existing boundaries.
|
|
130
|
+
*/
|
|
131
|
+
remove(start: number, end: number): void {
|
|
132
|
+
if (end <= start) {
|
|
133
|
+
throw new TypeError("End must be greater than start");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Find ranges that need to be modified
|
|
137
|
+
const affectedRanges: { index: number; range: Range<T> }[] = [];
|
|
138
|
+
for (let i = 0; i < this.ranges.length; i++) {
|
|
139
|
+
const range = this.ranges[i];
|
|
140
|
+
if (start < range.end && end > range.start) {
|
|
141
|
+
affectedRanges.push({ index: i, range });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (affectedRanges.length === 0) {
|
|
146
|
+
throw new Error("No ranges found to remove");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Verify boundaries match
|
|
150
|
+
if (start !== affectedRanges[0].range.start || end !== affectedRanges[affectedRanges.length - 1].range.end) {
|
|
151
|
+
throw new Error("Range boundaries must match existing boundaries");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Todo: also check if there's a gap in the middle but it should not happen with our usage
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < affectedRanges.length; i++) {
|
|
157
|
+
const { range } = affectedRanges[i];
|
|
158
|
+
|
|
159
|
+
range.refCount--;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.ranges = this.ranges.filter((range) => range.refCount > 0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get all ranges within the specified boundaries.
|
|
167
|
+
*/
|
|
168
|
+
getRanges(start: number, end: number): Range<T>[] {
|
|
169
|
+
if (end <= start) {
|
|
170
|
+
throw new TypeError("End must be greater than start");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return this.ranges.filter((range) => start < range.end && end > range.start);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get all ranges in the list
|
|
178
|
+
*/
|
|
179
|
+
getAllRanges(): Range<T>[] {
|
|
180
|
+
return [...this.ranges];
|
|
181
|
+
}
|
|
182
|
+
}
|