@qaecy/cue-cli 0.0.35 → 0.0.36
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/main.js +2941 -829
- package/package.json +2 -1
package/main.js
CHANGED
|
@@ -29,6 +29,223 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
mod
|
|
30
30
|
));
|
|
31
31
|
|
|
32
|
+
// libs/js/id-builders/src/lib/id-builders.ts
|
|
33
|
+
function contextBasedGuid(contextString, verbose = false) {
|
|
34
|
+
const namespace = "daca0510-72b5-48ba-9091-b918ca18136b";
|
|
35
|
+
contextString = replaceSpecialChars(contextString, verbose);
|
|
36
|
+
return (0, import_uuid2.v5)(contextString, namespace);
|
|
37
|
+
}
|
|
38
|
+
function replaceSpecialChars(str, verbose = false) {
|
|
39
|
+
let result = str;
|
|
40
|
+
for (const char in replacements) {
|
|
41
|
+
result = result.replace(new RegExp(char, "g"), replacements[char]);
|
|
42
|
+
}
|
|
43
|
+
if (verbose && result !== str)
|
|
44
|
+
console.info(`${str} -> ${result}`);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function generateFileUUID(filepath, providerId = "") {
|
|
48
|
+
return contextBasedGuid(`${providerId}${filepath}`);
|
|
49
|
+
}
|
|
50
|
+
var import_uuid2, replacements;
|
|
51
|
+
var init_id_builders = __esm({
|
|
52
|
+
"libs/js/id-builders/src/lib/id-builders.ts"() {
|
|
53
|
+
import_uuid2 = require("uuid");
|
|
54
|
+
replacements = {
|
|
55
|
+
"\xE4": "ae",
|
|
56
|
+
"a\u0308": "ae",
|
|
57
|
+
"\xC4": "AE",
|
|
58
|
+
"\xF6": "oe",
|
|
59
|
+
"\xD6": "OE",
|
|
60
|
+
"\xFC": "ue",
|
|
61
|
+
"u\u0308": "ue",
|
|
62
|
+
"\xDC": "UE",
|
|
63
|
+
"U\u0308": "UE",
|
|
64
|
+
"\xDF": "ss",
|
|
65
|
+
"\xE6": "ae",
|
|
66
|
+
"\xC6": "AE",
|
|
67
|
+
"\xF8": "oe",
|
|
68
|
+
"\xD8": "OE",
|
|
69
|
+
"\xE5": "aa",
|
|
70
|
+
"\xC5": "AA",
|
|
71
|
+
"\xE1": "a",
|
|
72
|
+
"\xC1": "A",
|
|
73
|
+
"\xF0": "d",
|
|
74
|
+
"\xD0": "D",
|
|
75
|
+
"\xE9": "e",
|
|
76
|
+
"\xC9": "E",
|
|
77
|
+
"\xED": "i",
|
|
78
|
+
"\xCD": "I",
|
|
79
|
+
"\xF3": "o",
|
|
80
|
+
"\xD3": "O",
|
|
81
|
+
"\xFA": "u",
|
|
82
|
+
"\xDA": "U",
|
|
83
|
+
"\xFD": "y",
|
|
84
|
+
"\xDD": "Y",
|
|
85
|
+
"\xFE": "th",
|
|
86
|
+
"\xDE": "Th"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// libs/js/id-builders/src/lib/id-extractor.ts
|
|
92
|
+
var extractIdsFromPath, extractIdsFromRawPath, extractIdsFromPathDerived;
|
|
93
|
+
var init_id_extractor = __esm({
|
|
94
|
+
"libs/js/id-builders/src/lib/id-extractor.ts"() {
|
|
95
|
+
extractIdsFromPath = (filePath) => {
|
|
96
|
+
if (filePath.startsWith("/")) {
|
|
97
|
+
filePath = filePath.slice(1);
|
|
98
|
+
}
|
|
99
|
+
const pathParts = filePath.split("/");
|
|
100
|
+
const projectId = pathParts[0];
|
|
101
|
+
const fileName = pathParts.pop();
|
|
102
|
+
let documentUUID = "";
|
|
103
|
+
let suffix = ".";
|
|
104
|
+
let processor = "";
|
|
105
|
+
let fileUUID;
|
|
106
|
+
if (fileName !== void 0) {
|
|
107
|
+
const fnParts = fileName.split("_");
|
|
108
|
+
documentUUID = fnParts[0];
|
|
109
|
+
if (documentUUID.includes("?"))
|
|
110
|
+
documentUUID = documentUUID.split("?")[0];
|
|
111
|
+
let processorPart = fnParts[1];
|
|
112
|
+
if (fnParts.length === 3) {
|
|
113
|
+
fileUUID = fnParts[1];
|
|
114
|
+
processorPart = fnParts[2];
|
|
115
|
+
}
|
|
116
|
+
suffix += processorPart.split(".").pop()?.toLowerCase() ?? "";
|
|
117
|
+
processor = processorPart.split(".")[0] ?? "";
|
|
118
|
+
}
|
|
119
|
+
const identifier = fileUUID !== void 0 ? `${documentUUID}_${fileUUID}` : documentUUID;
|
|
120
|
+
return { projectId, identifier, suffix, processor, documentUUID, fileUUID };
|
|
121
|
+
};
|
|
122
|
+
extractIdsFromRawPath = (filePath) => {
|
|
123
|
+
if (filePath.startsWith("/")) {
|
|
124
|
+
filePath = filePath.slice(1);
|
|
125
|
+
}
|
|
126
|
+
const pathParts = filePath.split("/");
|
|
127
|
+
const projectId = pathParts[0];
|
|
128
|
+
const fileName = pathParts.pop() ?? "";
|
|
129
|
+
const suffix = `.${fileName.split(".").pop()?.toLowerCase()}`;
|
|
130
|
+
const documentUUID = fileName.replace(/\.[^.]+$/, "");
|
|
131
|
+
return { projectId, documentUUID, suffix };
|
|
132
|
+
};
|
|
133
|
+
extractIdsFromPathDerived = (filePath) => {
|
|
134
|
+
if (filePath.startsWith("/")) {
|
|
135
|
+
filePath = filePath.slice(1);
|
|
136
|
+
}
|
|
137
|
+
const pathParts = filePath.split("/");
|
|
138
|
+
const projectId = pathParts[0];
|
|
139
|
+
const dir = pathParts[1];
|
|
140
|
+
let identifier = pathParts[2];
|
|
141
|
+
if (identifier.includes("?"))
|
|
142
|
+
identifier = identifier.split("?")[0];
|
|
143
|
+
if (identifier.includes("."))
|
|
144
|
+
identifier = identifier.replace(/\.[^.]+$/, "");
|
|
145
|
+
const fileName = pathParts.pop() ?? "";
|
|
146
|
+
const suffix = `.${fileName.split(".").pop()?.toLowerCase()}`;
|
|
147
|
+
const documentUUID = fileName.replace(/\.[^.]+$/, "");
|
|
148
|
+
return { projectId, documentUUID, suffix, identifier, dir };
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// libs/js/id-builders/src/lib/id-validator.ts
|
|
154
|
+
var isValidUUID;
|
|
155
|
+
var init_id_validator = __esm({
|
|
156
|
+
"libs/js/id-builders/src/lib/id-validator.ts"() {
|
|
157
|
+
isValidUUID = (uuid) => {
|
|
158
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
159
|
+
return uuidRegex.test(uuid);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// libs/js/id-builders/src/lib/md5-builder.ts
|
|
165
|
+
async function fromString(text) {
|
|
166
|
+
return new Promise((resolve2) => {
|
|
167
|
+
const spark = new import_spark_md5.default();
|
|
168
|
+
spark.append(text);
|
|
169
|
+
const hash = spark.end();
|
|
170
|
+
resolve2(hash);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function fromBuffer(file) {
|
|
174
|
+
return new Promise((resolve2) => {
|
|
175
|
+
const spark = new import_spark_md5.default.ArrayBuffer();
|
|
176
|
+
spark.append(file);
|
|
177
|
+
const hash = spark.end();
|
|
178
|
+
resolve2(hash);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async function fromReadStream(readStream) {
|
|
182
|
+
return new Promise((resolve2, reject) => {
|
|
183
|
+
const spark = new import_spark_md5.default.ArrayBuffer();
|
|
184
|
+
readStream.on("data", (chunk) => {
|
|
185
|
+
spark.append(chunk);
|
|
186
|
+
});
|
|
187
|
+
readStream.on("end", () => {
|
|
188
|
+
resolve2(spark.end());
|
|
189
|
+
});
|
|
190
|
+
readStream.on("error", (err) => reject(err));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function fromFile(file, verbose = false) {
|
|
194
|
+
return new Promise((resolve2, reject) => {
|
|
195
|
+
const blobSlice = File.prototype.slice, chunkSize = 2097152, chunks = Math.ceil(file.size / chunkSize), spark = new import_spark_md5.default.ArrayBuffer(), fileReader = new FileReader();
|
|
196
|
+
let currentChunk = 0;
|
|
197
|
+
fileReader.onload = function(e) {
|
|
198
|
+
if (verbose)
|
|
199
|
+
console.log("read chunk nr", currentChunk + 1, "of", chunks);
|
|
200
|
+
spark.append(e.target.result);
|
|
201
|
+
currentChunk++;
|
|
202
|
+
if (currentChunk < chunks) {
|
|
203
|
+
loadNext();
|
|
204
|
+
} else {
|
|
205
|
+
const hash = spark.end();
|
|
206
|
+
resolve2(hash);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
fileReader.onerror = function() {
|
|
210
|
+
reject("Building MD5 failed");
|
|
211
|
+
};
|
|
212
|
+
function loadNext() {
|
|
213
|
+
const start = currentChunk * chunkSize, end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
|
214
|
+
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
|
215
|
+
}
|
|
216
|
+
loadNext();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
var import_spark_md5;
|
|
220
|
+
var init_md5_builder = __esm({
|
|
221
|
+
"libs/js/id-builders/src/lib/md5-builder.ts"() {
|
|
222
|
+
import_spark_md5 = __toESM(require("spark-md5"));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// libs/js/id-builders/src/index.ts
|
|
227
|
+
var src_exports = {};
|
|
228
|
+
__export(src_exports, {
|
|
229
|
+
contextBasedGuid: () => contextBasedGuid,
|
|
230
|
+
extractIdsFromPath: () => extractIdsFromPath,
|
|
231
|
+
extractIdsFromPathDerived: () => extractIdsFromPathDerived,
|
|
232
|
+
extractIdsFromRawPath: () => extractIdsFromRawPath,
|
|
233
|
+
fromBuffer: () => fromBuffer,
|
|
234
|
+
fromFile: () => fromFile,
|
|
235
|
+
fromReadStream: () => fromReadStream,
|
|
236
|
+
fromString: () => fromString,
|
|
237
|
+
generateFileUUID: () => generateFileUUID,
|
|
238
|
+
isValidUUID: () => isValidUUID
|
|
239
|
+
});
|
|
240
|
+
var init_src = __esm({
|
|
241
|
+
"libs/js/id-builders/src/index.ts"() {
|
|
242
|
+
init_id_builders();
|
|
243
|
+
init_id_extractor();
|
|
244
|
+
init_id_validator();
|
|
245
|
+
init_md5_builder();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
32
249
|
// libs/js/sync-tools/src/lib/helpers/worker-pool.js
|
|
33
250
|
var worker_pool_exports = {};
|
|
34
251
|
__export(worker_pool_exports, {
|
|
@@ -91,10 +308,73 @@ var init_worker_pool = __esm({
|
|
|
91
308
|
}
|
|
92
309
|
});
|
|
93
310
|
|
|
311
|
+
// libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts
|
|
312
|
+
var md5_builder_node_exports = {};
|
|
313
|
+
__export(md5_builder_node_exports, {
|
|
314
|
+
md5FromWorker: () => md5FromWorker,
|
|
315
|
+
md5NoWorker: () => md5NoWorker
|
|
316
|
+
});
|
|
317
|
+
async function md5FromWorker(filePaths, hashWorkerPath, verbose = false, logIntervalPct = 1) {
|
|
318
|
+
const { WorkerPool: WorkerPool2 } = await Promise.resolve().then(() => (init_worker_pool(), worker_pool_exports));
|
|
319
|
+
const pool = new WorkerPool2(hashWorkerPath);
|
|
320
|
+
const hashes = filePaths.map((filePath) => pool.hashFile(filePath));
|
|
321
|
+
if (verbose) {
|
|
322
|
+
let completed = 0;
|
|
323
|
+
let lastPct = 0;
|
|
324
|
+
hashes.forEach(
|
|
325
|
+
(promise) => promise.then(() => {
|
|
326
|
+
completed++;
|
|
327
|
+
const pct = Math.floor(completed / filePaths.length * 100);
|
|
328
|
+
if (pct - lastPct >= logIntervalPct) {
|
|
329
|
+
lastPct = pct;
|
|
330
|
+
console.info(`MD5 progress: ${completed}/${filePaths.length} (${pct}%)`);
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
const ret = await Promise.all(hashes);
|
|
336
|
+
await pool.close();
|
|
337
|
+
return ret;
|
|
338
|
+
}
|
|
339
|
+
async function md5NoWorker(filePaths, verbose) {
|
|
340
|
+
if (verbose)
|
|
341
|
+
console.info(`Calculating MD5 hashes for ${filePaths.length} files...`);
|
|
342
|
+
const concurrency = 50;
|
|
343
|
+
const hashes = [];
|
|
344
|
+
for (let i = 0; i < filePaths.length; i += concurrency) {
|
|
345
|
+
const chunk = filePaths.slice(i, i + concurrency);
|
|
346
|
+
const chunkHashes = await Promise.all(
|
|
347
|
+
chunk.map(async (f) => {
|
|
348
|
+
const { createReadStream: createReadStream5 } = await import("fs");
|
|
349
|
+
const { fromReadStream: fromReadStream2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
350
|
+
const stream = createReadStream5(f);
|
|
351
|
+
return fromReadStream2(stream);
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
hashes.push(...chunkHashes);
|
|
355
|
+
if (verbose) {
|
|
356
|
+
const pct = Math.min(
|
|
357
|
+
100,
|
|
358
|
+
Math.round((i + chunk.length) / filePaths.length * 100)
|
|
359
|
+
);
|
|
360
|
+
console.info(
|
|
361
|
+
`MD5 progress: ${pct}% (${i + chunk.length}/${filePaths.length})`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (verbose)
|
|
366
|
+
console.info(`Calculated MD5 hashes for ${filePaths.length} files.`);
|
|
367
|
+
return hashes;
|
|
368
|
+
}
|
|
369
|
+
var init_md5_builder_node = __esm({
|
|
370
|
+
"libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts"() {
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
94
374
|
// apps/desktop/cue-cli/src/main.ts
|
|
95
375
|
var import_commander = require("commander");
|
|
96
|
-
var
|
|
97
|
-
var
|
|
376
|
+
var import_fs8 = require("fs");
|
|
377
|
+
var import_path4 = require("path");
|
|
98
378
|
|
|
99
379
|
// apps/desktop/cue-cli/src/variables.ts
|
|
100
380
|
var import_path = require("path");
|
|
@@ -237,63 +517,763 @@ var COLLECTION_USER_TERMS_ACCEPT = "userTermsAcceptance";
|
|
|
237
517
|
var COLLECTION_TIERS = "tiers";
|
|
238
518
|
|
|
239
519
|
// libs/js/firebase/src/lib/firebase.ts
|
|
240
|
-
var
|
|
241
|
-
var
|
|
520
|
+
var import_storage2 = require("firebase/storage");
|
|
521
|
+
var import_firestore2 = require("firebase/firestore");
|
|
242
522
|
var import_auth = require("firebase/auth");
|
|
243
523
|
var import_app = require("firebase/app");
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
524
|
+
|
|
525
|
+
// libs/js/databases/src/lib/graph/fuseki.ts
|
|
526
|
+
var Fuseki = class _Fuseki {
|
|
527
|
+
queryEndpoint;
|
|
528
|
+
updateEndpoint;
|
|
529
|
+
baseHeaders;
|
|
530
|
+
static RELEVANT_HEADER_KEYS = [
|
|
531
|
+
"authorization",
|
|
532
|
+
"Authorization",
|
|
533
|
+
"x-project-id"
|
|
534
|
+
];
|
|
535
|
+
constructor(graphOptions) {
|
|
536
|
+
this.queryEndpoint = graphOptions.queryEndpoint;
|
|
537
|
+
this.updateEndpoint = graphOptions.updateEndpoint;
|
|
538
|
+
this.baseHeaders = Object.fromEntries(
|
|
539
|
+
Object.entries(graphOptions.originalHeaders || {}).filter(
|
|
540
|
+
([key]) => _Fuseki.RELEVANT_HEADER_KEYS.includes(key)
|
|
541
|
+
)
|
|
542
|
+
);
|
|
543
|
+
if (graphOptions.authHeader !== void 0) {
|
|
544
|
+
this.baseHeaders["Authorization"] = graphOptions.authHeader;
|
|
259
545
|
}
|
|
260
|
-
return _CueFirebase._instance;
|
|
261
546
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
_functionInviteUserToProject;
|
|
267
|
-
_functionRemoveUserFromProject;
|
|
268
|
-
_storageProcessed;
|
|
269
|
-
_storageRaw;
|
|
270
|
-
_storageLogs;
|
|
271
|
-
_storageChatSessions;
|
|
272
|
-
_storagePublic;
|
|
273
|
-
_storagePersistence;
|
|
274
|
-
_collectionChatSessions;
|
|
275
|
-
_collectionOrganizations;
|
|
276
|
-
_collectionProjects;
|
|
277
|
-
_collectionRDFWriting;
|
|
278
|
-
_collectionAPIKeys;
|
|
279
|
-
_collectionUsers;
|
|
280
|
-
_collectionUserTermsAcceptance;
|
|
281
|
-
_collectionTiers;
|
|
282
|
-
_auth;
|
|
283
|
-
_app;
|
|
284
|
-
get functionAcceptTerms() {
|
|
285
|
-
return this._functionAcceptTerms;
|
|
547
|
+
async ping() {
|
|
548
|
+
const query4 = "ASK { }";
|
|
549
|
+
const res = await this.query(query4);
|
|
550
|
+
return res.boolean;
|
|
286
551
|
}
|
|
287
|
-
|
|
288
|
-
|
|
552
|
+
async query(query4, accept = "application/sparql-results+json") {
|
|
553
|
+
let res;
|
|
554
|
+
try {
|
|
555
|
+
res = await fetch(this.queryEndpoint, {
|
|
556
|
+
headers: {
|
|
557
|
+
...this.baseHeaders,
|
|
558
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
559
|
+
Accept: accept
|
|
560
|
+
},
|
|
561
|
+
method: "POST",
|
|
562
|
+
body: new URLSearchParams({ query: query4 })
|
|
563
|
+
});
|
|
564
|
+
} catch (err) {
|
|
565
|
+
throw new Error(
|
|
566
|
+
`Fuseki is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
if (!res.ok) {
|
|
570
|
+
const body = await res.text();
|
|
571
|
+
throw new Error(`Fuseki query failed (HTTP ${res.status}): ${body}`);
|
|
572
|
+
}
|
|
573
|
+
return await res.json();
|
|
289
574
|
}
|
|
290
|
-
|
|
291
|
-
|
|
575
|
+
async subset(query4, accept = "text/turtle") {
|
|
576
|
+
const res = await fetch(this.queryEndpoint, {
|
|
577
|
+
headers: {
|
|
578
|
+
...this.baseHeaders,
|
|
579
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
580
|
+
Authorization: this.baseHeaders["Authorization"] || "",
|
|
581
|
+
Accept: accept
|
|
582
|
+
},
|
|
583
|
+
method: "POST",
|
|
584
|
+
body: new URLSearchParams({ query: query4 })
|
|
585
|
+
});
|
|
586
|
+
if (accept === "application/ld+json") {
|
|
587
|
+
return await res.json();
|
|
588
|
+
}
|
|
589
|
+
return await res.text();
|
|
292
590
|
}
|
|
293
|
-
|
|
294
|
-
|
|
591
|
+
async update(update) {
|
|
592
|
+
const res = await fetch(this.updateEndpoint, {
|
|
593
|
+
headers: {
|
|
594
|
+
...this.baseHeaders,
|
|
595
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
596
|
+
},
|
|
597
|
+
method: "POST",
|
|
598
|
+
body: new URLSearchParams({ update })
|
|
599
|
+
});
|
|
600
|
+
if (!res.ok) {
|
|
601
|
+
const body = await res.text();
|
|
602
|
+
throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
|
|
603
|
+
}
|
|
604
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
605
|
+
if (contentType.includes("application/json")) {
|
|
606
|
+
return await res.json();
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
ok: res.ok,
|
|
610
|
+
status: res.status,
|
|
611
|
+
message: await res.text()
|
|
612
|
+
};
|
|
295
613
|
}
|
|
296
|
-
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// libs/js/databases/src/lib/graph/qlever.ts
|
|
617
|
+
var import_n3 = require("n3");
|
|
618
|
+
var QLeverLockedError = class extends Error {
|
|
619
|
+
constructor(body) {
|
|
620
|
+
super(`QLever is locked (rebuild in progress): ${body}`);
|
|
621
|
+
this.name = "QLeverLockedError";
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
var QLever = class _QLever {
|
|
625
|
+
queryEndpoint;
|
|
626
|
+
updateEndpoint;
|
|
627
|
+
dataEndpoint;
|
|
628
|
+
baseHeaders;
|
|
629
|
+
static RELEVANT_HEADER_KEYS = [
|
|
630
|
+
"authorization",
|
|
631
|
+
"Authorization",
|
|
632
|
+
"x-project-id",
|
|
633
|
+
"x-user-roles"
|
|
634
|
+
// add more if needed
|
|
635
|
+
];
|
|
636
|
+
/** Max retries on 423 Locked (rebuild in progress). */
|
|
637
|
+
static LOCKED_MAX_RETRIES = parseInt(
|
|
638
|
+
(typeof process !== "undefined" ? process.env["QLEVER_LOCKED_MAX_RETRIES"] : void 0) ?? "10",
|
|
639
|
+
10
|
|
640
|
+
);
|
|
641
|
+
/** Base delay (ms) for exponential backoff on 423. */
|
|
642
|
+
static LOCKED_BASE_DELAY_MS = parseInt(
|
|
643
|
+
(typeof process !== "undefined" ? process.env["QLEVER_LOCKED_BASE_DELAY_MS"] : void 0) ?? "2000",
|
|
644
|
+
10
|
|
645
|
+
);
|
|
646
|
+
/**
|
|
647
|
+
* Retry an async write operation on 423 Locked with exponential backoff + jitter.
|
|
648
|
+
* 423 means qlever accessor has an ongoing rebuild; we should wait and retry.
|
|
649
|
+
*/
|
|
650
|
+
static async _retryOnLocked(fn) {
|
|
651
|
+
const maxRetries = _QLever.LOCKED_MAX_RETRIES;
|
|
652
|
+
const baseDelayMs = _QLever.LOCKED_BASE_DELAY_MS;
|
|
653
|
+
let lastError;
|
|
654
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
655
|
+
try {
|
|
656
|
+
return await fn();
|
|
657
|
+
} catch (err) {
|
|
658
|
+
lastError = err;
|
|
659
|
+
if (err instanceof QLeverLockedError && attempt < maxRetries) {
|
|
660
|
+
const jitter = 0.5 + Math.random();
|
|
661
|
+
const delay = baseDelayMs * Math.pow(2, attempt) * jitter;
|
|
662
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
throw lastError;
|
|
669
|
+
}
|
|
670
|
+
constructor(graphOptions) {
|
|
671
|
+
this.queryEndpoint = graphOptions.queryEndpoint;
|
|
672
|
+
this.updateEndpoint = graphOptions.updateEndpoint;
|
|
673
|
+
this.dataEndpoint = this.updateEndpoint.replace(/\/update$/, "/data");
|
|
674
|
+
this.baseHeaders = Object.fromEntries(
|
|
675
|
+
Object.entries(graphOptions.originalHeaders || {}).filter(
|
|
676
|
+
([key]) => _QLever.RELEVANT_HEADER_KEYS.includes(key)
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
this.baseHeaders["x-user-roles"] = "admin";
|
|
680
|
+
}
|
|
681
|
+
async ping() {
|
|
682
|
+
const query4 = "ASK { }";
|
|
683
|
+
const res = await this.query(query4);
|
|
684
|
+
return res.boolean;
|
|
685
|
+
}
|
|
686
|
+
async query(query4, accept = "application/sparql-results+json") {
|
|
687
|
+
let res;
|
|
688
|
+
try {
|
|
689
|
+
res = await fetch(this.queryEndpoint, {
|
|
690
|
+
headers: {
|
|
691
|
+
...this.baseHeaders,
|
|
692
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
693
|
+
Accept: accept
|
|
694
|
+
},
|
|
695
|
+
method: "POST",
|
|
696
|
+
body: new URLSearchParams({ query: query4 })
|
|
697
|
+
});
|
|
698
|
+
} catch (err) {
|
|
699
|
+
throw new Error(
|
|
700
|
+
`QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
if (!res.ok) {
|
|
704
|
+
const body = await res.text();
|
|
705
|
+
throw new Error(`QLever query failed (HTTP ${res.status}): ${body}`);
|
|
706
|
+
}
|
|
707
|
+
return await res.json();
|
|
708
|
+
}
|
|
709
|
+
async subset(query4, accept = "text/turtle") {
|
|
710
|
+
const res = await fetch(this.queryEndpoint, {
|
|
711
|
+
headers: {
|
|
712
|
+
...this.baseHeaders,
|
|
713
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
714
|
+
Accept: accept
|
|
715
|
+
},
|
|
716
|
+
method: "POST",
|
|
717
|
+
body: new URLSearchParams({ query: query4 })
|
|
718
|
+
});
|
|
719
|
+
return await res.text();
|
|
720
|
+
}
|
|
721
|
+
async update(update) {
|
|
722
|
+
return _QLever._retryOnLocked(async () => {
|
|
723
|
+
const res = await fetch(this.updateEndpoint, {
|
|
724
|
+
headers: {
|
|
725
|
+
...this.baseHeaders,
|
|
726
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
727
|
+
},
|
|
728
|
+
method: "POST",
|
|
729
|
+
body: new URLSearchParams({ update })
|
|
730
|
+
});
|
|
731
|
+
if (!res.ok) {
|
|
732
|
+
const body = await res.text();
|
|
733
|
+
if (res.status === 423)
|
|
734
|
+
throw new QLeverLockedError(body);
|
|
735
|
+
throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
|
|
736
|
+
}
|
|
737
|
+
return await res.json();
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Insert quads via the /data endpoint, grouped by named graph.
|
|
742
|
+
* This is preferred over SPARQL INSERT DATA for QLever because
|
|
743
|
+
* the /data endpoint correctly registers named graphs in the index.
|
|
744
|
+
*/
|
|
745
|
+
async insertData(quads) {
|
|
746
|
+
await this._postToDataEndpoint(quads, this.dataEndpoint);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Delete quads via the /data/delete endpoint, grouped by named graph.
|
|
750
|
+
*/
|
|
751
|
+
async deleteData(quads) {
|
|
752
|
+
await this._postToDataEndpoint(quads, `${this.dataEndpoint}/delete`);
|
|
753
|
+
}
|
|
754
|
+
async _postToDataEndpoint(quads, baseUrl) {
|
|
755
|
+
const nquads = await this._quadsToNQuads(quads);
|
|
756
|
+
const body = await _gzip(Buffer.from(nquads, "utf-8"));
|
|
757
|
+
await _QLever._retryOnLocked(async () => {
|
|
758
|
+
const res = await fetch(baseUrl, {
|
|
759
|
+
method: "POST",
|
|
760
|
+
headers: {
|
|
761
|
+
...this.baseHeaders,
|
|
762
|
+
"Content-Type": "application/n-quads",
|
|
763
|
+
"Content-Encoding": "gzip"
|
|
764
|
+
},
|
|
765
|
+
body
|
|
766
|
+
});
|
|
767
|
+
if (!res.ok) {
|
|
768
|
+
const text = await res.text();
|
|
769
|
+
if (res.status === 423)
|
|
770
|
+
throw new QLeverLockedError(text);
|
|
771
|
+
throw new Error(`QLever data POST failed (HTTP ${res.status}): ${text}`);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
_quadsToNQuads(quads) {
|
|
776
|
+
return new Promise((resolve2, reject) => {
|
|
777
|
+
const writer = new import_n3.Writer({ format: "application/n-quads" });
|
|
778
|
+
writer.addQuads(quads);
|
|
779
|
+
writer.end((err, result) => err ? reject(err) : resolve2(result));
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
async function _gzip(input) {
|
|
784
|
+
const cs = new CompressionStream("gzip");
|
|
785
|
+
const writer = cs.writable.getWriter();
|
|
786
|
+
writer.write(new Uint8Array(input));
|
|
787
|
+
writer.close();
|
|
788
|
+
const chunks = [];
|
|
789
|
+
const reader = cs.readable.getReader();
|
|
790
|
+
while (true) {
|
|
791
|
+
const { done, value } = await reader.read();
|
|
792
|
+
if (done)
|
|
793
|
+
break;
|
|
794
|
+
chunks.push(value);
|
|
795
|
+
}
|
|
796
|
+
const total = chunks.reduce((n, c) => n + c.byteLength, 0);
|
|
797
|
+
const out = new Uint8Array(total);
|
|
798
|
+
let offset = 0;
|
|
799
|
+
for (const chunk of chunks) {
|
|
800
|
+
out.set(chunk, offset);
|
|
801
|
+
offset += chunk.byteLength;
|
|
802
|
+
}
|
|
803
|
+
return out.buffer;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// libs/js/databases/src/lib/graph/main.ts
|
|
807
|
+
var CueGraphDatabase = class {
|
|
808
|
+
constructor(options) {
|
|
809
|
+
this.options = options;
|
|
810
|
+
switch (this.options.graphType) {
|
|
811
|
+
case "qlever":
|
|
812
|
+
this._db = new QLever(this.options);
|
|
813
|
+
break;
|
|
814
|
+
case "fuseki":
|
|
815
|
+
this._db = new Fuseki(this.options);
|
|
816
|
+
break;
|
|
817
|
+
default:
|
|
818
|
+
throw new Error(`Unsupported graph type: ${this.options.graphType}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
_db;
|
|
822
|
+
ping() {
|
|
823
|
+
return this._db.ping();
|
|
824
|
+
}
|
|
825
|
+
query(queryString, accept) {
|
|
826
|
+
return this._db.query(queryString, accept);
|
|
827
|
+
}
|
|
828
|
+
subset(queryString, accept) {
|
|
829
|
+
if (this.options.graphType === "qlever") {
|
|
830
|
+
if (accept && accept !== "text/turtle") {
|
|
831
|
+
return Promise.reject(
|
|
832
|
+
new Error(
|
|
833
|
+
`QLever only supports 'text/turtle' for CONSTRUCT/DESCRIBE queries.`
|
|
834
|
+
)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return this._db.subset(
|
|
838
|
+
queryString,
|
|
839
|
+
accept
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
return this._db.subset(queryString, accept);
|
|
843
|
+
}
|
|
844
|
+
update(updateString) {
|
|
845
|
+
return this._db.update(updateString);
|
|
846
|
+
}
|
|
847
|
+
/** Returns true if this backend supports the /data bulk-insert endpoint (QLever only). */
|
|
848
|
+
supportsDataEndpoint() {
|
|
849
|
+
return this.options.graphType === "qlever";
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Insert quads using the backend's preferred bulk-insert mechanism.
|
|
853
|
+
* For QLever: uses the /data endpoint (correctly registers named graphs).
|
|
854
|
+
* For Fuseki: falls back to SPARQL INSERT DATA.
|
|
855
|
+
*/
|
|
856
|
+
insertData(quads) {
|
|
857
|
+
if (this.options.graphType === "qlever") {
|
|
858
|
+
return this._db.insertData(quads);
|
|
859
|
+
}
|
|
860
|
+
return Promise.reject(new Error("insertData not supported for Fuseki \u2014 use update() with SPARQL INSERT DATA"));
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Delete quads using the backend's preferred bulk-delete mechanism.
|
|
864
|
+
* For QLever: uses the /data/delete endpoint.
|
|
865
|
+
* For Fuseki: falls back to SPARQL DELETE DATA.
|
|
866
|
+
*/
|
|
867
|
+
deleteData(quads) {
|
|
868
|
+
if (this.options.graphType === "qlever") {
|
|
869
|
+
return this._db.deleteData(quads);
|
|
870
|
+
}
|
|
871
|
+
return Promise.reject(new Error("deleteData not supported for Fuseki \u2014 use update() with SPARQL DELETE DATA"));
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// libs/js/databases/src/lib/blob/blob.ts
|
|
876
|
+
var import_storage = require("firebase/storage");
|
|
877
|
+
var CueBlobStorage = class {
|
|
878
|
+
constructor(options) {
|
|
879
|
+
this.options = options;
|
|
880
|
+
}
|
|
881
|
+
/** Paths known to be absent, keyed as `bucket:path`. Avoids repeat 404 requests. */
|
|
882
|
+
_knownMissing = /* @__PURE__ */ new Set();
|
|
883
|
+
// ─── Storage bucket resolution ────────────────────────────────────────────
|
|
884
|
+
_bucket(name) {
|
|
885
|
+
switch (name) {
|
|
886
|
+
case "raw":
|
|
887
|
+
return this.options.storageRaw;
|
|
888
|
+
case "processed":
|
|
889
|
+
return this.options.storageProcessed;
|
|
890
|
+
case "logs":
|
|
891
|
+
return this.options.storageLogs;
|
|
892
|
+
case "chatSessions":
|
|
893
|
+
return this.options.storageChatSessions;
|
|
894
|
+
case "public":
|
|
895
|
+
return this.options.storagePublic;
|
|
896
|
+
case "persistence":
|
|
897
|
+
return this.options.storagePersistence;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
// ─── Downloads ────────────────────────────────────────────────────────────
|
|
901
|
+
/** Get an authenticated download URL. Returns undefined if the file does not exist. */
|
|
902
|
+
async getDownloadURL(bucket, path) {
|
|
903
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
904
|
+
try {
|
|
905
|
+
return await (0, import_storage.getDownloadURL)(fileRef);
|
|
906
|
+
} catch (err) {
|
|
907
|
+
if (err?.code === "storage/object-not-found")
|
|
908
|
+
return void 0;
|
|
909
|
+
throw err;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Download URL with a `?t=<updated>` suffix, bypassing caches.
|
|
914
|
+
* Returns undefined if the file does not exist.
|
|
915
|
+
*/
|
|
916
|
+
async getCacheBustedUrl(bucket, path) {
|
|
917
|
+
const key = `${bucket}:${path}`;
|
|
918
|
+
if (this._knownMissing.has(key))
|
|
919
|
+
return void 0;
|
|
920
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
921
|
+
try {
|
|
922
|
+
const metadata = await (0, import_storage.getMetadata)(fileRef);
|
|
923
|
+
const url = await (0, import_storage.getDownloadURL)(fileRef);
|
|
924
|
+
return `${url}&t=${encodeURIComponent(metadata.updated)}`;
|
|
925
|
+
} catch (err) {
|
|
926
|
+
if (err?.code === "storage/object-not-found" || err?.status === 404) {
|
|
927
|
+
this._knownMissing.add(key);
|
|
928
|
+
console.debug(`[CueBlobStorage] ${path} not found (404 OK \u2014 optional cache file)`);
|
|
929
|
+
return void 0;
|
|
930
|
+
}
|
|
931
|
+
throw err;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/** Download a file as a Blob. Returns undefined if the file does not exist. */
|
|
935
|
+
async getFile(bucket, path) {
|
|
936
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
937
|
+
try {
|
|
938
|
+
return await (0, import_storage.getBlob)(fileRef);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
if (err?.code === "storage/object-not-found")
|
|
941
|
+
return void 0;
|
|
942
|
+
throw err;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/** Returns true if the file exists. */
|
|
946
|
+
async fileExists(bucket, path) {
|
|
947
|
+
return await this.getDownloadURL(bucket, path) !== void 0;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Download a file from the public bucket and return its contents as a string.
|
|
951
|
+
* Uses a cache-busted URL to ensure the latest version is fetched.
|
|
952
|
+
*/
|
|
953
|
+
async downloadPublic(blobName) {
|
|
954
|
+
const fileRef = (0, import_storage.ref)(this.options.storagePublic, blobName);
|
|
955
|
+
const controller = new AbortController();
|
|
956
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
957
|
+
const raceAbort = (p) => Promise.race([
|
|
958
|
+
p,
|
|
959
|
+
new Promise((_, reject) => {
|
|
960
|
+
controller.signal.addEventListener(
|
|
961
|
+
"abort",
|
|
962
|
+
() => reject(new DOMException(`Download timed out: ${blobName}`, "AbortError"))
|
|
963
|
+
);
|
|
964
|
+
})
|
|
965
|
+
]);
|
|
966
|
+
try {
|
|
967
|
+
const [url, metadata] = await Promise.all([
|
|
968
|
+
raceAbort((0, import_storage.getDownloadURL)(fileRef)),
|
|
969
|
+
raceAbort((0, import_storage.getMetadata)(fileRef))
|
|
970
|
+
]);
|
|
971
|
+
const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
|
|
972
|
+
const res = await fetch(cacheBustedUrl, { signal: controller.signal });
|
|
973
|
+
if (!res.ok)
|
|
974
|
+
throw new Error(`HTTP ${res.status}`);
|
|
975
|
+
return res.text();
|
|
976
|
+
} catch (err) {
|
|
977
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
978
|
+
throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
|
|
979
|
+
} finally {
|
|
980
|
+
clearTimeout(timeout);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// ─── Metadata ─────────────────────────────────────────────────────────────
|
|
984
|
+
/** Read file metadata. Returns undefined if the file does not exist. */
|
|
985
|
+
async getMetadata(bucket, path) {
|
|
986
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
987
|
+
try {
|
|
988
|
+
const m = await (0, import_storage.getMetadata)(fileRef);
|
|
989
|
+
return {
|
|
990
|
+
updated: m.updated,
|
|
991
|
+
contentType: m.contentType,
|
|
992
|
+
size: m.size,
|
|
993
|
+
customMetadata: m.customMetadata
|
|
994
|
+
};
|
|
995
|
+
} catch (err) {
|
|
996
|
+
if (err?.code === "storage/object-not-found")
|
|
997
|
+
return void 0;
|
|
998
|
+
throw err;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/** Update custom metadata on an existing file. */
|
|
1002
|
+
async setMetadata(bucket, path, customMetadata) {
|
|
1003
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
1004
|
+
await (0, import_storage.updateMetadata)(fileRef, { customMetadata });
|
|
1005
|
+
}
|
|
1006
|
+
// ─── Uploads ──────────────────────────────────────────────────────────────
|
|
1007
|
+
/**
|
|
1008
|
+
* Resumable upload. Returns an {@link UploadHandle} with progress callbacks
|
|
1009
|
+
* and pause/resume/cancel controls. Use for large files or files where
|
|
1010
|
+
* upload progress needs to be surfaced in the UI.
|
|
1011
|
+
*/
|
|
1012
|
+
uploadResumable(bucket, path, data, customMetadata) {
|
|
1013
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
1014
|
+
const task = (0, import_storage.uploadBytesResumable)(fileRef, data, customMetadata ? { customMetadata } : void 0);
|
|
1015
|
+
return {
|
|
1016
|
+
complete: () => new Promise((resolve2, reject) => task.then(() => resolve2(), reject)),
|
|
1017
|
+
pause: () => task.pause(),
|
|
1018
|
+
resume: () => task.resume(),
|
|
1019
|
+
cancel: () => task.cancel(),
|
|
1020
|
+
onProgress: (cb) => {
|
|
1021
|
+
const unsub = task.on("state_changed", (snap) => {
|
|
1022
|
+
cb(snap.bytesTransferred, snap.totalBytes);
|
|
1023
|
+
});
|
|
1024
|
+
return () => unsub();
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Simple one-shot upload. Waits for completion before resolving.
|
|
1030
|
+
* Use for small files where progress feedback is not needed.
|
|
1031
|
+
*/
|
|
1032
|
+
async uploadBytes(bucket, path, data, customMetadata) {
|
|
1033
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
1034
|
+
await (0, import_storage.uploadBytes)(fileRef, data, customMetadata ? { customMetadata } : void 0);
|
|
1035
|
+
}
|
|
1036
|
+
/** Upload a string or base64-encoded value. */
|
|
1037
|
+
async uploadString(bucket, path, data, format = import_storage.StringFormat.RAW, customMetadata) {
|
|
1038
|
+
const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
|
|
1039
|
+
await (0, import_storage.uploadString)(fileRef, data, format, customMetadata ? { customMetadata } : void 0);
|
|
1040
|
+
}
|
|
1041
|
+
// ─── Listing ──────────────────────────────────────────────────────────────
|
|
1042
|
+
/** List all item names directly under `prefix` in the given bucket. */
|
|
1043
|
+
async listFiles(bucket, prefix) {
|
|
1044
|
+
const listRef = (0, import_storage.ref)(this._bucket(bucket), prefix);
|
|
1045
|
+
const result = await (0, import_storage.listAll)(listRef);
|
|
1046
|
+
return result.items.map((item) => item.name);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Recursively list all file full-paths under `prefix` in the given bucket.
|
|
1050
|
+
* Returns full storage paths (not just names).
|
|
1051
|
+
*/
|
|
1052
|
+
async listFilesRecursive(bucket, prefix) {
|
|
1053
|
+
const storage = this._bucket(bucket);
|
|
1054
|
+
const collectFiles = async (path) => {
|
|
1055
|
+
const listResult = await (0, import_storage.listAll)((0, import_storage.ref)(storage, path));
|
|
1056
|
+
let files = listResult.items.map((item) => item.fullPath);
|
|
1057
|
+
for (const folder of listResult.prefixes) {
|
|
1058
|
+
files = files.concat(await collectFiles(folder.fullPath));
|
|
1059
|
+
}
|
|
1060
|
+
return files;
|
|
1061
|
+
};
|
|
1062
|
+
return collectFiles(prefix);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Delete all files under `prefix` recursively.
|
|
1066
|
+
* Returns the number of files deleted.
|
|
1067
|
+
*/
|
|
1068
|
+
async deleteDirectory(bucket, prefix) {
|
|
1069
|
+
const { deleteObject } = await import("firebase/storage");
|
|
1070
|
+
const storage = this._bucket(bucket);
|
|
1071
|
+
let deletedCount = 0;
|
|
1072
|
+
const deleteAll = async (path) => {
|
|
1073
|
+
const listResult = await (0, import_storage.listAll)((0, import_storage.ref)(storage, path));
|
|
1074
|
+
for (const item of listResult.items) {
|
|
1075
|
+
await deleteObject(item);
|
|
1076
|
+
deletedCount++;
|
|
1077
|
+
}
|
|
1078
|
+
for (const folder of listResult.prefixes) {
|
|
1079
|
+
await deleteAll(folder.fullPath);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
await deleteAll(prefix);
|
|
1083
|
+
return deletedCount;
|
|
1084
|
+
}
|
|
1085
|
+
// ─── Legacy helpers kept for CueBlobStorage consumers ────────────────────
|
|
1086
|
+
// (used by loaders / processors that were already depending on CueBlobStorage)
|
|
1087
|
+
/** Upload binary data to the raw bucket with retry logic. */
|
|
1088
|
+
async uploadRaw(blobName, data, metadata, maxRetries = 3, signal, onProgress) {
|
|
1089
|
+
const fileRef = (0, import_storage.ref)(this.options.storageRaw, blobName);
|
|
1090
|
+
let attempt = 0;
|
|
1091
|
+
let lastError;
|
|
1092
|
+
while (attempt < maxRetries) {
|
|
1093
|
+
try {
|
|
1094
|
+
await new Promise((resolve2, reject) => {
|
|
1095
|
+
if (signal?.aborted) {
|
|
1096
|
+
reject(new DOMException("Upload cancelled", "AbortError"));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const task = (0, import_storage.uploadBytesResumable)(fileRef, data, { customMetadata: metadata });
|
|
1100
|
+
const onAbort = () => {
|
|
1101
|
+
task.cancel();
|
|
1102
|
+
reject(new DOMException("Upload cancelled", "AbortError"));
|
|
1103
|
+
};
|
|
1104
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1105
|
+
task.on(
|
|
1106
|
+
"state_changed",
|
|
1107
|
+
(snapshot) => {
|
|
1108
|
+
if (onProgress) {
|
|
1109
|
+
const pct = Math.round(snapshot.bytesTransferred / snapshot.totalBytes * 100);
|
|
1110
|
+
onProgress(pct);
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
(error) => {
|
|
1114
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1115
|
+
reject(error);
|
|
1116
|
+
},
|
|
1117
|
+
() => {
|
|
1118
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1119
|
+
resolve2();
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
});
|
|
1123
|
+
return;
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
if (signal?.aborted || err instanceof DOMException && err.name === "AbortError") {
|
|
1126
|
+
throw err;
|
|
1127
|
+
}
|
|
1128
|
+
lastError = err;
|
|
1129
|
+
attempt++;
|
|
1130
|
+
if (attempt < maxRetries) {
|
|
1131
|
+
await new Promise((res) => setTimeout(res, 1e3 * attempt));
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
throw lastError;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Upload data to the processed bucket.
|
|
1139
|
+
* Skips upload and returns `false` if the blob already exists.
|
|
1140
|
+
*/
|
|
1141
|
+
async uploadProcessed(blobName, data, metadata) {
|
|
1142
|
+
const fileRef = (0, import_storage.ref)(this.options.storageProcessed, blobName);
|
|
1143
|
+
const existing = await (0, import_storage.getMetadata)(fileRef).catch(() => null);
|
|
1144
|
+
if (existing)
|
|
1145
|
+
return false;
|
|
1146
|
+
await (0, import_storage.uploadBytes)(fileRef, data, { customMetadata: metadata });
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
/** List all blob names directly under `prefix` in the raw bucket. */
|
|
1150
|
+
async listRaw(prefix) {
|
|
1151
|
+
const listRef = (0, import_storage.ref)(this.options.storageRaw, prefix);
|
|
1152
|
+
const result = await (0, import_storage.listAll)(listRef);
|
|
1153
|
+
return result.items.map((item) => item.name);
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
// libs/js/databases/src/lib/doc/firestore.ts
|
|
1158
|
+
var import_firestore = require("firebase/firestore");
|
|
1159
|
+
var FirestoreDocStore = class {
|
|
1160
|
+
_db;
|
|
1161
|
+
constructor(app) {
|
|
1162
|
+
this._db = (0, import_firestore.getFirestore)(app);
|
|
1163
|
+
}
|
|
1164
|
+
subscribeToDoc(col, id, callback) {
|
|
1165
|
+
const ref6 = (0, import_firestore.doc)(this._db, col, id);
|
|
1166
|
+
return (0, import_firestore.onSnapshot)(ref6, (snapshot) => {
|
|
1167
|
+
callback(snapshot.exists() ? snapshot.data() : null);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
subscribeToCollection(col, filters, callback, options) {
|
|
1171
|
+
const constraints = this._buildConstraints(filters, options);
|
|
1172
|
+
const q = (0, import_firestore.query)((0, import_firestore.collection)(this._db, col), ...constraints);
|
|
1173
|
+
return (0, import_firestore.onSnapshot)(q, (snapshot) => {
|
|
1174
|
+
callback(
|
|
1175
|
+
snapshot.docs.map((d) => ({ id: d.id, data: d.data() }))
|
|
1176
|
+
);
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
async getDoc(col, id) {
|
|
1180
|
+
const ref6 = (0, import_firestore.doc)(this._db, col, id);
|
|
1181
|
+
const snapshot = await (0, import_firestore.getDoc)(ref6);
|
|
1182
|
+
return snapshot.exists() ? snapshot.data() : null;
|
|
1183
|
+
}
|
|
1184
|
+
async queryDocs(col, filters, options) {
|
|
1185
|
+
const constraints = this._buildConstraints(filters, options);
|
|
1186
|
+
const q = (0, import_firestore.query)((0, import_firestore.collection)(this._db, col), ...constraints);
|
|
1187
|
+
const snapshot = await (0, import_firestore.getDocs)(q);
|
|
1188
|
+
return snapshot.docs.map((d) => ({ id: d.id, data: d.data() }));
|
|
1189
|
+
}
|
|
1190
|
+
async setDoc(col, id, data) {
|
|
1191
|
+
const ref6 = (0, import_firestore.doc)(this._db, col, id);
|
|
1192
|
+
await (0, import_firestore.setDoc)(ref6, data);
|
|
1193
|
+
}
|
|
1194
|
+
async updateDoc(col, id, data) {
|
|
1195
|
+
const ref6 = (0, import_firestore.doc)(this._db, col, id);
|
|
1196
|
+
await (0, import_firestore.updateDoc)(ref6, data);
|
|
1197
|
+
}
|
|
1198
|
+
async addDoc(col, data) {
|
|
1199
|
+
const ref6 = await (0, import_firestore.addDoc)((0, import_firestore.collection)(this._db, col), data);
|
|
1200
|
+
return ref6.id;
|
|
1201
|
+
}
|
|
1202
|
+
async deleteDoc(col, id) {
|
|
1203
|
+
const ref6 = (0, import_firestore.doc)(this._db, col, id);
|
|
1204
|
+
await (0, import_firestore.deleteDoc)(ref6);
|
|
1205
|
+
}
|
|
1206
|
+
_buildConstraints(filters, options) {
|
|
1207
|
+
const constraints = filters.map(
|
|
1208
|
+
(f) => (0, import_firestore.where)(f.field, f.op, f.value)
|
|
1209
|
+
);
|
|
1210
|
+
const extras = [];
|
|
1211
|
+
if (options?.orderBy) {
|
|
1212
|
+
extras.push((0, import_firestore.orderBy)(options.orderBy.field, options.orderBy.direction));
|
|
1213
|
+
}
|
|
1214
|
+
if (options?.limit) {
|
|
1215
|
+
extras.push((0, import_firestore.limit)(options.limit));
|
|
1216
|
+
}
|
|
1217
|
+
return [...constraints, ...extras];
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// libs/js/firebase/src/lib/firebase.ts
|
|
1222
|
+
var CueFirebase = class _CueFirebase {
|
|
1223
|
+
static _instance;
|
|
1224
|
+
_muted = true;
|
|
1225
|
+
_emulator = false;
|
|
1226
|
+
constructor(config, auth, muted = false) {
|
|
1227
|
+
this._muted = muted;
|
|
1228
|
+
this._init(config, auth);
|
|
1229
|
+
}
|
|
1230
|
+
static getInstance(config, auth, muted = false) {
|
|
1231
|
+
if (!_CueFirebase._instance) {
|
|
1232
|
+
if (config === void 0)
|
|
1233
|
+
throw new Error("Config needed for instantiation!");
|
|
1234
|
+
if (!muted)
|
|
1235
|
+
console.info("Creating new CueFirebase instance");
|
|
1236
|
+
_CueFirebase._instance = new _CueFirebase(config, auth, muted);
|
|
1237
|
+
}
|
|
1238
|
+
return _CueFirebase._instance;
|
|
1239
|
+
}
|
|
1240
|
+
_functionAcceptTerms;
|
|
1241
|
+
_functionEmitMessage;
|
|
1242
|
+
_functionGetUserInfo;
|
|
1243
|
+
_functionChangeUserRoleOnProject;
|
|
1244
|
+
_functionInviteUserToProject;
|
|
1245
|
+
_functionRemoveUserFromProject;
|
|
1246
|
+
_storageProcessed;
|
|
1247
|
+
_storageRaw;
|
|
1248
|
+
_storageLogs;
|
|
1249
|
+
_storageChatSessions;
|
|
1250
|
+
_storagePublic;
|
|
1251
|
+
_storagePersistence;
|
|
1252
|
+
_collectionChatSessions;
|
|
1253
|
+
_collectionOrganizations;
|
|
1254
|
+
_collectionProjects;
|
|
1255
|
+
_collectionRDFWriting;
|
|
1256
|
+
_collectionAPIKeys;
|
|
1257
|
+
_collectionUsers;
|
|
1258
|
+
_collectionUserTermsAcceptance;
|
|
1259
|
+
_collectionTiers;
|
|
1260
|
+
_auth;
|
|
1261
|
+
_app;
|
|
1262
|
+
_docStore;
|
|
1263
|
+
_blobStore;
|
|
1264
|
+
get functionAcceptTerms() {
|
|
1265
|
+
return this._functionAcceptTerms;
|
|
1266
|
+
}
|
|
1267
|
+
get functionEmitMessage() {
|
|
1268
|
+
return this._functionEmitMessage;
|
|
1269
|
+
}
|
|
1270
|
+
get functionGetUserInfo() {
|
|
1271
|
+
return this._functionGetUserInfo;
|
|
1272
|
+
}
|
|
1273
|
+
get functionInviteUserToProject() {
|
|
1274
|
+
return this._functionInviteUserToProject;
|
|
1275
|
+
}
|
|
1276
|
+
get functionChangeUserRoleOnProject() {
|
|
297
1277
|
return this._functionChangeUserRoleOnProject;
|
|
298
1278
|
}
|
|
299
1279
|
get functionRemoveUserFromProject() {
|
|
@@ -347,6 +1327,14 @@ var CueFirebase = class _CueFirebase {
|
|
|
347
1327
|
get auth() {
|
|
348
1328
|
return this._auth;
|
|
349
1329
|
}
|
|
1330
|
+
/** Provider-agnostic document store backed by Firestore. */
|
|
1331
|
+
get docStore() {
|
|
1332
|
+
return this._docStore;
|
|
1333
|
+
}
|
|
1334
|
+
/** Provider-agnostic blob store backed by Firebase Storage. */
|
|
1335
|
+
get blobStore() {
|
|
1336
|
+
return this._blobStore;
|
|
1337
|
+
}
|
|
350
1338
|
get emulatorMode() {
|
|
351
1339
|
return this._emulator;
|
|
352
1340
|
}
|
|
@@ -383,31 +1371,40 @@ var CueFirebase = class _CueFirebase {
|
|
|
383
1371
|
functions,
|
|
384
1372
|
FUNCTION_REMOVE_USER_FROM_PROJECT_PATH
|
|
385
1373
|
);
|
|
386
|
-
this._storageProcessed = (0,
|
|
387
|
-
this._storageRaw = (0,
|
|
388
|
-
this._storageChatSessions = (0,
|
|
389
|
-
this._storageLogs = (0,
|
|
390
|
-
this._storagePublic = (0,
|
|
391
|
-
this._storagePersistence = (0,
|
|
392
|
-
this._collectionChatSessions = (0,
|
|
393
|
-
(0,
|
|
1374
|
+
this._storageProcessed = (0, import_storage2.getStorage)(app, BUCKET_PROCESSED);
|
|
1375
|
+
this._storageRaw = (0, import_storage2.getStorage)(app, BUCKET_RAW);
|
|
1376
|
+
this._storageChatSessions = (0, import_storage2.getStorage)(app, BUCKET_CHAT_SESSIONS);
|
|
1377
|
+
this._storageLogs = (0, import_storage2.getStorage)(app, BUCKET_LOGS);
|
|
1378
|
+
this._storagePublic = (0, import_storage2.getStorage)(app, BUCKET_PUBLIC);
|
|
1379
|
+
this._storagePersistence = (0, import_storage2.getStorage)(app, BUCKET_PERSISTENCE);
|
|
1380
|
+
this._collectionChatSessions = (0, import_firestore2.collection)(
|
|
1381
|
+
(0, import_firestore2.getFirestore)(app),
|
|
394
1382
|
COLLECTION_CHAT_SESSIONS
|
|
395
1383
|
);
|
|
396
|
-
this._collectionOrganizations = (0,
|
|
397
|
-
(0,
|
|
1384
|
+
this._collectionOrganizations = (0, import_firestore2.collection)(
|
|
1385
|
+
(0, import_firestore2.getFirestore)(app),
|
|
398
1386
|
COLLECTION_ORGANIZATIONS
|
|
399
1387
|
);
|
|
400
|
-
this._collectionProjects = (0,
|
|
401
|
-
this._collectionRDFWriting = (0,
|
|
402
|
-
this._collectionAPIKeys = (0,
|
|
403
|
-
this._collectionUsers = (0,
|
|
404
|
-
this._collectionUserTermsAcceptance = (0,
|
|
405
|
-
(0,
|
|
1388
|
+
this._collectionProjects = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_PROJECTS);
|
|
1389
|
+
this._collectionRDFWriting = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_RDF_WRITING);
|
|
1390
|
+
this._collectionAPIKeys = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_API_KEYS);
|
|
1391
|
+
this._collectionUsers = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_USERS);
|
|
1392
|
+
this._collectionUserTermsAcceptance = (0, import_firestore2.collection)(
|
|
1393
|
+
(0, import_firestore2.getFirestore)(app),
|
|
406
1394
|
COLLECTION_USER_TERMS_ACCEPT
|
|
407
1395
|
);
|
|
408
|
-
this._collectionTiers = (0,
|
|
1396
|
+
this._collectionTiers = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_TIERS);
|
|
409
1397
|
this._auth = auth === void 0 ? (0, import_auth.getAuth)(app) : auth;
|
|
410
1398
|
this._app = app;
|
|
1399
|
+
this._docStore = new FirestoreDocStore(app);
|
|
1400
|
+
this._blobStore = new CueBlobStorage({
|
|
1401
|
+
storageRaw: this._storageRaw,
|
|
1402
|
+
storageProcessed: this._storageProcessed,
|
|
1403
|
+
storageLogs: this._storageLogs,
|
|
1404
|
+
storageChatSessions: this._storageChatSessions,
|
|
1405
|
+
storagePublic: this._storagePublic,
|
|
1406
|
+
storagePersistence: this._storagePersistence
|
|
1407
|
+
});
|
|
411
1408
|
this._emulator = config.useEmulator || false;
|
|
412
1409
|
if (config.useEmulator) {
|
|
413
1410
|
this.attachEmulators();
|
|
@@ -452,14 +1449,14 @@ var CueFirebase = class _CueFirebase {
|
|
|
452
1449
|
}
|
|
453
1450
|
const functions = (0, import_functions.getFunctions)(this._app, GCP_REGION);
|
|
454
1451
|
(0, import_auth.connectAuthEmulator)(this._auth, authEmulatorUrl);
|
|
455
|
-
(0,
|
|
1452
|
+
(0, import_firestore2.connectFirestoreEmulator)((0, import_firestore2.getFirestore)(this._app), firestoreHost, firestorePort);
|
|
456
1453
|
(0, import_functions.connectFunctionsEmulator)(functions, "localhost", 5001);
|
|
457
|
-
(0,
|
|
458
|
-
(0,
|
|
459
|
-
(0,
|
|
460
|
-
(0,
|
|
461
|
-
(0,
|
|
462
|
-
(0,
|
|
1454
|
+
(0, import_storage2.connectStorageEmulator)(this._storageProcessed, storageHost, storagePort);
|
|
1455
|
+
(0, import_storage2.connectStorageEmulator)(this._storageRaw, storageHost, storagePort);
|
|
1456
|
+
(0, import_storage2.connectStorageEmulator)(this._storageChatSessions, storageHost, storagePort);
|
|
1457
|
+
(0, import_storage2.connectStorageEmulator)(this._storageLogs, storageHost, storagePort);
|
|
1458
|
+
(0, import_storage2.connectStorageEmulator)(this._storagePublic, storageHost, storagePort);
|
|
1459
|
+
(0, import_storage2.connectStorageEmulator)(this._storagePersistence, storageHost, storagePort);
|
|
463
1460
|
if (!this._muted)
|
|
464
1461
|
console.info("Firebase emulators attached");
|
|
465
1462
|
}
|
|
@@ -469,7 +1466,7 @@ var CueFirebase = class _CueFirebase {
|
|
|
469
1466
|
var import_uuid = require("uuid");
|
|
470
1467
|
|
|
471
1468
|
// libs/js/sync-tools/src/lib/list-remote-files.ts
|
|
472
|
-
var
|
|
1469
|
+
var import_storage3 = require("firebase/storage");
|
|
473
1470
|
|
|
474
1471
|
// libs/js/prefixes/src/lib/qaecy-prefixes.ts
|
|
475
1472
|
var qaecyPrefixes = {
|
|
@@ -3730,11 +4727,11 @@ async function listRemoteFiles(spaceId, providerId, queryHandler2, verbose = fal
|
|
|
3730
4727
|
const storage = firebase.storageRaw;
|
|
3731
4728
|
if (!storage)
|
|
3732
4729
|
throw new Error("Firebase storage is not initialized");
|
|
3733
|
-
const listRef = (0,
|
|
4730
|
+
const listRef = (0, import_storage3.ref)(storage, spaceId);
|
|
3734
4731
|
if (verbose)
|
|
3735
4732
|
console.info(`Listing files in raw space: ${spaceId}`);
|
|
3736
4733
|
const [blobFiles, locationDataMap] = await Promise.all([
|
|
3737
|
-
(0,
|
|
4734
|
+
(0, import_storage3.listAll)(listRef),
|
|
3738
4735
|
getGraphFiles(providerId, queryHandler2)
|
|
3739
4736
|
]);
|
|
3740
4737
|
const files = [];
|
|
@@ -3783,134 +4780,13 @@ async function getGraphFiles(providerId, queryHandler2) {
|
|
|
3783
4780
|
}
|
|
3784
4781
|
|
|
3785
4782
|
// libs/js/sync-tools/src/lib/list-local-files.ts
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
var import_uuid2 = require("uuid");
|
|
3790
|
-
var replacements = {
|
|
3791
|
-
"\xE4": "ae",
|
|
3792
|
-
"a\u0308": "ae",
|
|
3793
|
-
"\xC4": "AE",
|
|
3794
|
-
"\xF6": "oe",
|
|
3795
|
-
"\xD6": "OE",
|
|
3796
|
-
"\xFC": "ue",
|
|
3797
|
-
"u\u0308": "ue",
|
|
3798
|
-
"\xDC": "UE",
|
|
3799
|
-
"U\u0308": "UE",
|
|
3800
|
-
"\xDF": "ss",
|
|
3801
|
-
"\xE6": "ae",
|
|
3802
|
-
"\xC6": "AE",
|
|
3803
|
-
"\xF8": "oe",
|
|
3804
|
-
"\xD8": "OE",
|
|
3805
|
-
"\xE5": "aa",
|
|
3806
|
-
"\xC5": "AA",
|
|
3807
|
-
"\xE1": "a",
|
|
3808
|
-
"\xC1": "A",
|
|
3809
|
-
"\xF0": "d",
|
|
3810
|
-
"\xD0": "D",
|
|
3811
|
-
"\xE9": "e",
|
|
3812
|
-
"\xC9": "E",
|
|
3813
|
-
"\xED": "i",
|
|
3814
|
-
"\xCD": "I",
|
|
3815
|
-
"\xF3": "o",
|
|
3816
|
-
"\xD3": "O",
|
|
3817
|
-
"\xFA": "u",
|
|
3818
|
-
"\xDA": "U",
|
|
3819
|
-
"\xFD": "y",
|
|
3820
|
-
"\xDD": "Y",
|
|
3821
|
-
"\xFE": "th",
|
|
3822
|
-
"\xDE": "Th"
|
|
3823
|
-
};
|
|
3824
|
-
function contextBasedGuid(contextString, verbose = false) {
|
|
3825
|
-
const namespace = "daca0510-72b5-48ba-9091-b918ca18136b";
|
|
3826
|
-
contextString = replaceSpecialChars(contextString, verbose);
|
|
3827
|
-
return (0, import_uuid2.v5)(contextString, namespace);
|
|
3828
|
-
}
|
|
3829
|
-
function replaceSpecialChars(str, verbose = false) {
|
|
3830
|
-
let result = str;
|
|
3831
|
-
for (const char in replacements) {
|
|
3832
|
-
result = result.replace(new RegExp(char, "g"), replacements[char]);
|
|
3833
|
-
}
|
|
3834
|
-
if (verbose && result !== str)
|
|
3835
|
-
console.info(`${str} -> ${result}`);
|
|
3836
|
-
return result;
|
|
3837
|
-
}
|
|
3838
|
-
function generateFileUUID(filepath, providerId = "") {
|
|
3839
|
-
return contextBasedGuid(`${providerId}${filepath}`);
|
|
4783
|
+
init_src();
|
|
4784
|
+
async function _path() {
|
|
4785
|
+
return import("path");
|
|
3840
4786
|
}
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
var import_spark_md5 = __toESM(require("spark-md5"));
|
|
3844
|
-
async function fromReadStream(readStream) {
|
|
3845
|
-
return new Promise((resolve2, reject) => {
|
|
3846
|
-
const spark = new import_spark_md5.default.ArrayBuffer();
|
|
3847
|
-
readStream.on("data", (chunk) => {
|
|
3848
|
-
spark.append(chunk);
|
|
3849
|
-
});
|
|
3850
|
-
readStream.on("end", () => {
|
|
3851
|
-
resolve2(spark.end());
|
|
3852
|
-
});
|
|
3853
|
-
readStream.on("error", (err) => reject(err));
|
|
3854
|
-
});
|
|
3855
|
-
}
|
|
3856
|
-
|
|
3857
|
-
// libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts
|
|
3858
|
-
var import_fs = require("fs");
|
|
3859
|
-
async function md5FromWorker(filePaths, hashWorkerPath, verbose = false, logIntervalPct = 1) {
|
|
3860
|
-
const { WorkerPool: WorkerPool2 } = await Promise.resolve().then(() => (init_worker_pool(), worker_pool_exports));
|
|
3861
|
-
const pool = new WorkerPool2(hashWorkerPath);
|
|
3862
|
-
const hashes = filePaths.map((filePath) => pool.hashFile(filePath));
|
|
3863
|
-
if (verbose) {
|
|
3864
|
-
let completed = 0;
|
|
3865
|
-
let lastPct = 0;
|
|
3866
|
-
hashes.forEach(
|
|
3867
|
-
(promise) => promise.then(() => {
|
|
3868
|
-
completed++;
|
|
3869
|
-
const pct = Math.floor(completed / filePaths.length * 100);
|
|
3870
|
-
if (pct - lastPct >= logIntervalPct) {
|
|
3871
|
-
lastPct = pct;
|
|
3872
|
-
console.info(`MD5 progress: ${completed}/${filePaths.length} (${pct}%)`);
|
|
3873
|
-
}
|
|
3874
|
-
})
|
|
3875
|
-
);
|
|
3876
|
-
}
|
|
3877
|
-
const ret = await Promise.all(hashes);
|
|
3878
|
-
await pool.close();
|
|
3879
|
-
return ret;
|
|
3880
|
-
}
|
|
3881
|
-
async function md5NoWorker(filePaths, verbose) {
|
|
3882
|
-
if (verbose)
|
|
3883
|
-
console.info(`Calculating MD5 hashes for ${filePaths.length} files...`);
|
|
3884
|
-
const concurrency = 50;
|
|
3885
|
-
const hashes = [];
|
|
3886
|
-
for (let i = 0; i < filePaths.length; i += concurrency) {
|
|
3887
|
-
const chunk = filePaths.slice(i, i + concurrency);
|
|
3888
|
-
const chunkHashes = await Promise.all(
|
|
3889
|
-
chunk.map((f) => {
|
|
3890
|
-
const stream = (0, import_fs.createReadStream)(f);
|
|
3891
|
-
return fromReadStream(stream);
|
|
3892
|
-
})
|
|
3893
|
-
);
|
|
3894
|
-
hashes.push(...chunkHashes);
|
|
3895
|
-
if (verbose) {
|
|
3896
|
-
const pct = Math.min(
|
|
3897
|
-
100,
|
|
3898
|
-
Math.round((i + chunk.length) / filePaths.length * 100)
|
|
3899
|
-
);
|
|
3900
|
-
console.info(
|
|
3901
|
-
`MD5 progress: ${pct}% (${i + chunk.length}/${filePaths.length})`
|
|
3902
|
-
);
|
|
3903
|
-
}
|
|
3904
|
-
}
|
|
3905
|
-
if (verbose)
|
|
3906
|
-
console.info(`Calculated MD5 hashes for ${filePaths.length} files.`);
|
|
3907
|
-
return hashes;
|
|
4787
|
+
async function _fs() {
|
|
4788
|
+
return import("fs/promises");
|
|
3908
4789
|
}
|
|
3909
|
-
|
|
3910
|
-
// libs/js/sync-tools/src/lib/list-local-files.ts
|
|
3911
|
-
var import_promises = require("fs/promises");
|
|
3912
|
-
var import_promises2 = require("fs/promises");
|
|
3913
|
-
var import_jszip = __toESM(require("jszip"));
|
|
3914
4790
|
var IGNORED = {
|
|
3915
4791
|
dirs: ["node_modules", ".git", ".hg", ".svn", ".DS_Store"],
|
|
3916
4792
|
suffix: [".tmp", ".part", ".crdownload"]
|
|
@@ -3944,10 +4820,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
|
|
|
3944
4820
|
console.warn(
|
|
3945
4821
|
`More than 10,000 files found in ${dir}. This may take a while...`
|
|
3946
4822
|
);
|
|
3947
|
-
const
|
|
3948
|
-
const
|
|
4823
|
+
const { md5FromWorker: md5FromWorker2, md5NoWorker: md5NoWorker2 } = await Promise.resolve().then(() => (init_md5_builder_node(), md5_builder_node_exports));
|
|
4824
|
+
const hashes = hashWorkerPath ? await md5FromWorker2(fullPaths, hashWorkerPath, verbose, logIntervalPct) : await md5NoWorker2(fullPaths, verbose);
|
|
4825
|
+
const { stat } = await _fs();
|
|
4826
|
+
const { relative } = await _path();
|
|
4827
|
+
const stats = await Promise.all(fullPaths.map((f) => stat(f)));
|
|
3949
4828
|
return fullPaths.map((f, i) => {
|
|
3950
|
-
const relativePath =
|
|
4829
|
+
const relativePath = relative(dir, f);
|
|
3951
4830
|
const md5 = hashes[i];
|
|
3952
4831
|
const contentUUID = contextBasedGuid(md5);
|
|
3953
4832
|
const locationUUID = generateFileUUID(relativePath, providerId);
|
|
@@ -3957,11 +4836,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
|
|
|
3957
4836
|
}
|
|
3958
4837
|
async function filesInLocalDirRecursive(dir, filterFunc = () => true, excludeDirs = true) {
|
|
3959
4838
|
const results = [];
|
|
4839
|
+
const { readdir } = await _fs();
|
|
4840
|
+
const { join: join4 } = await _path();
|
|
3960
4841
|
async function traverseDir(currentDir) {
|
|
3961
4842
|
try {
|
|
3962
|
-
const entries = await
|
|
4843
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
3963
4844
|
for (const entry of entries) {
|
|
3964
|
-
const fullPath = (
|
|
4845
|
+
const fullPath = join4(currentDir, entry.name);
|
|
3965
4846
|
if (entry.isFile()) {
|
|
3966
4847
|
if (filterFunc(entry)) {
|
|
3967
4848
|
results.push(fullPath);
|
|
@@ -4003,23 +4884,26 @@ async function unzipFile(zipPath, recursive = true, options) {
|
|
|
4003
4884
|
if (currentDepth > maxRecursionDepth) {
|
|
4004
4885
|
throw new Error(`Zip extraction aborted: exceeded max recursion depth (${maxRecursionDepth})`);
|
|
4005
4886
|
}
|
|
4887
|
+
const { mkdir: mkdir2, readFile, writeFile: writeFile2 } = await _fs();
|
|
4888
|
+
const { join: join4 } = await _path();
|
|
4889
|
+
const { default: JSZip } = await import("jszip");
|
|
4006
4890
|
const targetDir = zipPath.replace(/\.zip$/i, "") + UNZIPPED_SUFFIX;
|
|
4007
|
-
await (
|
|
4008
|
-
const data = await
|
|
4009
|
-
const zip = await
|
|
4891
|
+
await mkdir2(targetDir, { recursive: true });
|
|
4892
|
+
const data = await readFile(zipPath);
|
|
4893
|
+
const zip = await JSZip.loadAsync(data);
|
|
4010
4894
|
const extractPromises = [];
|
|
4011
4895
|
for (const [relativePath, file] of Object.entries(zip.files)) {
|
|
4012
4896
|
if (!file.dir) {
|
|
4013
4897
|
const pathParts = relativePath.split("/");
|
|
4014
|
-
const fileTargetDir = (
|
|
4015
|
-
const targetPath = (
|
|
4898
|
+
const fileTargetDir = join4(targetDir, ...pathParts.slice(0, -1));
|
|
4899
|
+
const targetPath = join4(fileTargetDir, pathParts[pathParts.length - 1]);
|
|
4016
4900
|
await ensureDir(fileTargetDir);
|
|
4017
4901
|
const promise = file.async("nodebuffer").then(async (content) => {
|
|
4018
4902
|
totalUncompressedSize.value += content.length;
|
|
4019
4903
|
if (totalUncompressedSize.value > maxUncompressedSize) {
|
|
4020
4904
|
throw new Error(`Zip extraction aborted: exceeded max uncompressed size (${maxUncompressedSize} bytes)`);
|
|
4021
4905
|
}
|
|
4022
|
-
await (
|
|
4906
|
+
await writeFile2(targetPath, content);
|
|
4023
4907
|
if (recursive && targetPath.toLowerCase().endsWith(".zip")) {
|
|
4024
4908
|
await unzipFile(targetPath, true, {
|
|
4025
4909
|
maxUncompressedSize,
|
|
@@ -4035,18 +4919,20 @@ async function unzipFile(zipPath, recursive = true, options) {
|
|
|
4035
4919
|
await Promise.all(extractPromises);
|
|
4036
4920
|
}
|
|
4037
4921
|
async function ensureDir(path) {
|
|
4038
|
-
|
|
4922
|
+
const { mkdir: mkdir2 } = await _fs();
|
|
4923
|
+
await mkdir2(path, { recursive: true });
|
|
4039
4924
|
}
|
|
4040
4925
|
async function deleteUnzipped(dir) {
|
|
4926
|
+
const { rm } = await _fs();
|
|
4041
4927
|
const paths = await filesInLocalDirRecursive(dir, (entry) => entry.name.toLowerCase().endsWith(UNZIPPED_SUFFIX), false);
|
|
4042
4928
|
for (const p of paths) {
|
|
4043
|
-
await
|
|
4929
|
+
await rm(p, { recursive: true, force: true });
|
|
4044
4930
|
}
|
|
4045
4931
|
}
|
|
4046
4932
|
|
|
4047
4933
|
// apps/desktop/cue-cli/src/helpers/query-handler.ts
|
|
4048
4934
|
var import_auth2 = require("firebase/auth");
|
|
4049
|
-
async function queryHandler(
|
|
4935
|
+
async function queryHandler(query4, spaceId, useEmulator) {
|
|
4050
4936
|
const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
|
|
4051
4937
|
const token = await (0, import_auth2.getAuth)().currentUser?.getIdToken();
|
|
4052
4938
|
const res = await fetch(endpoint, {
|
|
@@ -4057,7 +4943,7 @@ async function queryHandler(query3, spaceId, useEmulator) {
|
|
|
4057
4943
|
"Content-Type": "application/sparql-query",
|
|
4058
4944
|
Accept: "application/sparql-results+json"
|
|
4059
4945
|
},
|
|
4060
|
-
body:
|
|
4946
|
+
body: query4
|
|
4061
4947
|
});
|
|
4062
4948
|
if (!res.ok) {
|
|
4063
4949
|
console.error(
|
|
@@ -4079,13 +4965,138 @@ function fileSizePretty(size) {
|
|
|
4079
4965
|
|
|
4080
4966
|
// libs/js/cue-sdk/src/lib/cue.ts
|
|
4081
4967
|
var import_app2 = require("firebase/app");
|
|
4968
|
+
var import_storage5 = require("firebase/storage");
|
|
4082
4969
|
|
|
4083
4970
|
// libs/js/cue-sdk/src/lib/auth.ts
|
|
4084
4971
|
var import_auth3 = require("firebase/auth");
|
|
4972
|
+
|
|
4973
|
+
// libs/js/cue-sdk/src/lib/signal.ts
|
|
4974
|
+
init_src();
|
|
4975
|
+
var CueSignal = class {
|
|
4976
|
+
_value;
|
|
4977
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
4978
|
+
constructor(initial) {
|
|
4979
|
+
this._value = initial;
|
|
4980
|
+
}
|
|
4981
|
+
get() {
|
|
4982
|
+
return this._value;
|
|
4983
|
+
}
|
|
4984
|
+
set(value) {
|
|
4985
|
+
this._value = value;
|
|
4986
|
+
for (const fn of this._listeners)
|
|
4987
|
+
fn();
|
|
4988
|
+
}
|
|
4989
|
+
subscribe(listener) {
|
|
4990
|
+
this._listeners.add(listener);
|
|
4991
|
+
return () => this._listeners.delete(listener);
|
|
4992
|
+
}
|
|
4993
|
+
/** Returns a read-only view of this signal. */
|
|
4994
|
+
asReadonly() {
|
|
4995
|
+
return this;
|
|
4996
|
+
}
|
|
4997
|
+
};
|
|
4998
|
+
function cueComputed(deps, compute) {
|
|
4999
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5000
|
+
let cached = compute();
|
|
5001
|
+
let dirty = false;
|
|
5002
|
+
const unsubs = [];
|
|
5003
|
+
const notify = () => {
|
|
5004
|
+
dirty = true;
|
|
5005
|
+
for (const fn of listeners)
|
|
5006
|
+
fn();
|
|
5007
|
+
};
|
|
5008
|
+
for (const dep of deps) {
|
|
5009
|
+
unsubs.push(dep.subscribe(notify));
|
|
5010
|
+
}
|
|
5011
|
+
return {
|
|
5012
|
+
get() {
|
|
5013
|
+
if (dirty) {
|
|
5014
|
+
cached = compute();
|
|
5015
|
+
dirty = false;
|
|
5016
|
+
}
|
|
5017
|
+
return cached;
|
|
5018
|
+
},
|
|
5019
|
+
subscribe(fn) {
|
|
5020
|
+
listeners.add(fn);
|
|
5021
|
+
return () => listeners.delete(fn);
|
|
5022
|
+
},
|
|
5023
|
+
destroy() {
|
|
5024
|
+
for (const unsub of unsubs)
|
|
5025
|
+
unsub();
|
|
5026
|
+
listeners.clear();
|
|
5027
|
+
}
|
|
5028
|
+
};
|
|
5029
|
+
}
|
|
5030
|
+
async function staleWhileRevalidate(query4, fetchFresh, onData, cache) {
|
|
5031
|
+
const cacheKey = contextBasedGuid(query4);
|
|
5032
|
+
let staleId;
|
|
5033
|
+
if (cache) {
|
|
5034
|
+
const stale = await cache.get(cacheKey);
|
|
5035
|
+
if (stale !== void 0) {
|
|
5036
|
+
onData(stale, true);
|
|
5037
|
+
staleId = contextBasedGuid(JSON.stringify(stale));
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
const fresh = await fetchFresh();
|
|
5041
|
+
onData(fresh, false);
|
|
5042
|
+
if (cache) {
|
|
5043
|
+
const freshId = contextBasedGuid(JSON.stringify(fresh));
|
|
5044
|
+
if (freshId !== staleId) {
|
|
5045
|
+
cache.set(cacheKey, fresh).catch(
|
|
5046
|
+
(err) => console.error("[staleWhileRevalidate] Cache write failed:", err)
|
|
5047
|
+
);
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
return fresh;
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
// libs/js/cue-sdk/src/variables.ts
|
|
5054
|
+
var DEFAULT_SDK_CONFIG = {
|
|
5055
|
+
apiKey: "AIzaSyAiW42QBx9HS4Khu88pCW7MV66IhBAQul0",
|
|
5056
|
+
appId: "1:151132927589:web:d2ffdb377dfadfd23ab88c",
|
|
5057
|
+
measurementId: "G-YT4PK6HGZD"
|
|
5058
|
+
};
|
|
5059
|
+
var FIREBASE_PROJECT_ID = "qaecy-mvp-406413";
|
|
5060
|
+
var FIREBASE_SENDER_ID = "734737865998";
|
|
5061
|
+
var GCP_REGION2 = "europe-west6";
|
|
5062
|
+
var COLLECTION_API_KEYS2 = "apiKeys";
|
|
5063
|
+
var COLLECTION_ORGANIZATIONS2 = "organizations";
|
|
5064
|
+
var COLLECTION_PROJECTS2 = "projects";
|
|
5065
|
+
var BUCKET_CHAT_SESSIONS2 = "spaces_chats_eu_west6";
|
|
5066
|
+
var BUCKET_RAW2 = "spaces_raw_eu_west6";
|
|
5067
|
+
var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
|
|
5068
|
+
var BUCKET_LOGS2 = "spaces_logs_eu_west6";
|
|
5069
|
+
var BUCKET_PUBLIC2 = "cue_public_eu_west6";
|
|
5070
|
+
var BUCKET_PERSISTENCE2 = "db_persistence_eu_west6";
|
|
5071
|
+
var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
|
|
5072
|
+
var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
|
|
5073
|
+
var ENDPOINT_SEARCH = "/assistant/search";
|
|
5074
|
+
var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
|
|
5075
|
+
var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
|
|
5076
|
+
var ENDPOINT_QLEVER_QUERY = "/qlever-server/qlever/query";
|
|
5077
|
+
var ENDPOINT_QLEVER_UPDATE = "/qlever-server/qlever/update";
|
|
5078
|
+
var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
|
|
4085
5079
|
var MICROSOFT_PROVIDER_ID = "microsoft.com";
|
|
5080
|
+
var SUPERADMIN_ROLE = "superadmin";
|
|
5081
|
+
var RESOURCE_BASE = "https://cue.qaecy.com/r/";
|
|
5082
|
+
|
|
5083
|
+
// libs/js/cue-sdk/src/lib/auth.ts
|
|
4086
5084
|
var CueAuth = class {
|
|
4087
5085
|
_auth;
|
|
4088
5086
|
_endpoints;
|
|
5087
|
+
_userSignal = new CueSignal(null);
|
|
5088
|
+
_tokenSignal = new CueSignal(null);
|
|
5089
|
+
_isSuperAdminSignal = new CueSignal(false);
|
|
5090
|
+
_userIdsSignal;
|
|
5091
|
+
_stopTokenListener;
|
|
5092
|
+
/** Reactive auth state — emits the signed-in `User`, or `null` when signed out. */
|
|
5093
|
+
user;
|
|
5094
|
+
/** Reactive Firebase ID token — refreshes automatically; `null` when signed out. */
|
|
5095
|
+
token;
|
|
5096
|
+
/** `true` when the current user has the `superadmin` custom claim. */
|
|
5097
|
+
isSuperAdmin;
|
|
5098
|
+
/** All unique UIDs for the current user (Firebase UID + linked provider UIDs). */
|
|
5099
|
+
userIds;
|
|
4089
5100
|
constructor(app, useEmulator = false, endpoints) {
|
|
4090
5101
|
this._auth = (0, import_auth3.getAuth)(app);
|
|
4091
5102
|
this._endpoints = endpoints;
|
|
@@ -4094,6 +5105,35 @@ var CueAuth = class {
|
|
|
4094
5105
|
disableWarnings: true
|
|
4095
5106
|
});
|
|
4096
5107
|
}
|
|
5108
|
+
this.user = this._userSignal.asReadonly();
|
|
5109
|
+
this.token = this._tokenSignal.asReadonly();
|
|
5110
|
+
this.isSuperAdmin = this._isSuperAdminSignal.asReadonly();
|
|
5111
|
+
this._userIdsSignal = cueComputed([this._userSignal], () => {
|
|
5112
|
+
const u = this._userSignal.get();
|
|
5113
|
+
if (!u)
|
|
5114
|
+
return [];
|
|
5115
|
+
const ids = /* @__PURE__ */ new Set([u.uid]);
|
|
5116
|
+
u.providerData.forEach((p) => ids.add(p.uid));
|
|
5117
|
+
return Array.from(ids);
|
|
5118
|
+
});
|
|
5119
|
+
this.userIds = this._userIdsSignal;
|
|
5120
|
+
this._stopTokenListener = (0, import_auth3.onIdTokenChanged)(this._auth, async (user) => {
|
|
5121
|
+
this._userSignal.set(user);
|
|
5122
|
+
if (user) {
|
|
5123
|
+
const token = await user.getIdToken();
|
|
5124
|
+
this._tokenSignal.set(token);
|
|
5125
|
+
const result = await (0, import_auth3.getIdTokenResult)(user);
|
|
5126
|
+
this._isSuperAdminSignal.set(result.claims["role"] === SUPERADMIN_ROLE);
|
|
5127
|
+
} else {
|
|
5128
|
+
this._tokenSignal.set(null);
|
|
5129
|
+
this._isSuperAdminSignal.set(false);
|
|
5130
|
+
}
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
/** Stop all internal Firebase listeners. Call when the `Cue` instance is no longer needed. */
|
|
5134
|
+
destroy() {
|
|
5135
|
+
this._stopTokenListener();
|
|
5136
|
+
this._userIdsSignal.destroy?.();
|
|
4097
5137
|
}
|
|
4098
5138
|
async signIn(provider, credentials) {
|
|
4099
5139
|
if (provider === "password") {
|
|
@@ -4110,6 +5150,36 @@ var CueAuth = class {
|
|
|
4110
5150
|
const result = await (0, import_auth3.signInWithPopup)(this._auth, firebaseProvider);
|
|
4111
5151
|
return result.user;
|
|
4112
5152
|
}
|
|
5153
|
+
/**
|
|
5154
|
+
* Initiate a redirect-based sign-in (mobile / iframe contexts where popups
|
|
5155
|
+
* are blocked). Call `checkRedirectResult()` on the next page load to
|
|
5156
|
+
* retrieve the result.
|
|
5157
|
+
*/
|
|
5158
|
+
async signInWithRedirect(provider) {
|
|
5159
|
+
const firebaseProvider = provider === "google" ? new import_auth3.GoogleAuthProvider() : new import_auth3.OAuthProvider(MICROSOFT_PROVIDER_ID);
|
|
5160
|
+
await (0, import_auth3.signInWithRedirect)(this._auth, firebaseProvider);
|
|
5161
|
+
}
|
|
5162
|
+
/**
|
|
5163
|
+
* Retrieve the result of a redirect sign-in. Returns the signed-in `User`
|
|
5164
|
+
* or `null` if there is no pending redirect result.
|
|
5165
|
+
* Call this once on app startup before showing a sign-in UI.
|
|
5166
|
+
*/
|
|
5167
|
+
async checkRedirectResult() {
|
|
5168
|
+
const result = await (0, import_auth3.getRedirectResult)(this._auth);
|
|
5169
|
+
return result?.user ?? null;
|
|
5170
|
+
}
|
|
5171
|
+
/**
|
|
5172
|
+
* One-shot async check — returns `true` if the current user has the
|
|
5173
|
+
* `superadmin` custom claim. For reactive use, read `cue.auth.isSuperAdmin`
|
|
5174
|
+
* (the signal) instead.
|
|
5175
|
+
*/
|
|
5176
|
+
async checkSuperAdmin() {
|
|
5177
|
+
const user = this._auth.currentUser;
|
|
5178
|
+
if (!user)
|
|
5179
|
+
return false;
|
|
5180
|
+
const tokenResult = await (0, import_auth3.getIdTokenResult)(user);
|
|
5181
|
+
return tokenResult.claims["role"] === SUPERADMIN_ROLE;
|
|
5182
|
+
}
|
|
4113
5183
|
/** Sign in with a Cue API key */
|
|
4114
5184
|
async signInWithApiKey(cueApiKey, projectId) {
|
|
4115
5185
|
const response = await fetch(this._endpoints.tokenUrl, {
|
|
@@ -4144,6 +5214,30 @@ var CueAuth = class {
|
|
|
4144
5214
|
return null;
|
|
4145
5215
|
return user.getIdToken(forceRefresh);
|
|
4146
5216
|
}
|
|
5217
|
+
/**
|
|
5218
|
+
* Executes a fetch with a Bearer token. On a 401 response the token is
|
|
5219
|
+
* force-refreshed and the request is retried once before throwing.
|
|
5220
|
+
*/
|
|
5221
|
+
async authenticatedFetch(url, init = {}) {
|
|
5222
|
+
const makeRequest = async (forceRefresh) => {
|
|
5223
|
+
const token = await this.getToken(forceRefresh);
|
|
5224
|
+
if (!token)
|
|
5225
|
+
throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
5226
|
+
const { headers: existingHeaders, ...rest } = init;
|
|
5227
|
+
return fetch(url, {
|
|
5228
|
+
...rest,
|
|
5229
|
+
headers: {
|
|
5230
|
+
...existingHeaders,
|
|
5231
|
+
Authorization: `Bearer ${token}`
|
|
5232
|
+
}
|
|
5233
|
+
});
|
|
5234
|
+
};
|
|
5235
|
+
let response = await makeRequest(false);
|
|
5236
|
+
if (response.status === 401) {
|
|
5237
|
+
response = await makeRequest(true);
|
|
5238
|
+
}
|
|
5239
|
+
return response;
|
|
5240
|
+
}
|
|
4147
5241
|
/** Raw Firebase Auth instance, for advanced use cases */
|
|
4148
5242
|
get firebaseAuth() {
|
|
4149
5243
|
return this._auth;
|
|
@@ -4151,9 +5245,6 @@ var CueAuth = class {
|
|
|
4151
5245
|
};
|
|
4152
5246
|
|
|
4153
5247
|
// libs/js/cue-sdk/src/lib/api.ts
|
|
4154
|
-
var ENDPOINT_SEARCH = "/assistant/search";
|
|
4155
|
-
var ENDPOINT_SPARQL = "/triplestore/query";
|
|
4156
|
-
var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
|
|
4157
5248
|
var CueApi = class {
|
|
4158
5249
|
constructor(_auth, _gatewayUrl, projects, sync) {
|
|
4159
5250
|
this._auth = _auth;
|
|
@@ -4161,9 +5252,6 @@ var CueApi = class {
|
|
|
4161
5252
|
this.projects = projects;
|
|
4162
5253
|
this.sync = sync;
|
|
4163
5254
|
}
|
|
4164
|
-
async _authHeaders() {
|
|
4165
|
-
return this.getAuthHeaders();
|
|
4166
|
-
}
|
|
4167
5255
|
/**
|
|
4168
5256
|
* Returns standard authentication headers for the current user.
|
|
4169
5257
|
* Useful when calling Cue-backed services directly (e.g. the GIS proxy).
|
|
@@ -4182,18 +5270,22 @@ var CueApi = class {
|
|
|
4182
5270
|
* The user must be authenticated before calling this.
|
|
4183
5271
|
*/
|
|
4184
5272
|
async search(request) {
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
5273
|
+
const response = await this._auth.authenticatedFetch(
|
|
5274
|
+
`${this._gatewayUrl}${ENDPOINT_SEARCH}`,
|
|
5275
|
+
{
|
|
5276
|
+
method: "POST",
|
|
5277
|
+
headers: { "Content-Type": "application/json", "cue-project-id": request.projectId },
|
|
5278
|
+
body: JSON.stringify({
|
|
5279
|
+
term: request.term,
|
|
5280
|
+
projectId: request.projectId,
|
|
5281
|
+
categories: request.categories ?? []
|
|
5282
|
+
})
|
|
5283
|
+
}
|
|
5284
|
+
);
|
|
4195
5285
|
if (!response.ok) {
|
|
4196
|
-
throw new Error(
|
|
5286
|
+
throw new Error(
|
|
5287
|
+
`Search request failed: ${response.status} ${response.statusText}`
|
|
5288
|
+
);
|
|
4197
5289
|
}
|
|
4198
5290
|
return response.json();
|
|
4199
5291
|
}
|
|
@@ -4201,48 +5293,71 @@ var CueApi = class {
|
|
|
4201
5293
|
* Execute a SPARQL query against the project's triplestore.
|
|
4202
5294
|
* The user must be authenticated before calling this.
|
|
4203
5295
|
*/
|
|
4204
|
-
async sparql(
|
|
4205
|
-
|
|
4206
|
-
const
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
5296
|
+
async sparql(query4, projectId, graphType) {
|
|
5297
|
+
console.log(graphType);
|
|
5298
|
+
const endpoint = graphType === "qlever" ? ENDPOINT_QLEVER_QUERY : ENDPOINT_FUSEKI_QUERY;
|
|
5299
|
+
console.log(`Executing SPARQL query against ${endpoint} for project ${projectId} with graph type ${graphType ?? "fuseki"}`);
|
|
5300
|
+
const urlencoded = new URLSearchParams();
|
|
5301
|
+
urlencoded.append("query", query4);
|
|
5302
|
+
const response = await this._auth.authenticatedFetch(
|
|
5303
|
+
`${this._gatewayUrl}${endpoint}`,
|
|
5304
|
+
{
|
|
5305
|
+
method: "POST",
|
|
5306
|
+
headers: {
|
|
5307
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
5308
|
+
"Accept": "application/sparql-results+json",
|
|
5309
|
+
"x-project-id": projectId,
|
|
5310
|
+
"cue-project-id": projectId
|
|
5311
|
+
},
|
|
5312
|
+
body: urlencoded
|
|
5313
|
+
}
|
|
5314
|
+
);
|
|
4211
5315
|
if (!response.ok) {
|
|
4212
|
-
throw new Error(
|
|
5316
|
+
throw new Error(
|
|
5317
|
+
`SPARQL query failed: ${response.status} ${response.statusText}`
|
|
5318
|
+
);
|
|
4213
5319
|
}
|
|
4214
5320
|
return response.json();
|
|
4215
5321
|
}
|
|
4216
|
-
async getConsumption(projectId
|
|
4217
|
-
const
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
5322
|
+
async getConsumption(projectId) {
|
|
5323
|
+
const response = await this._auth.authenticatedFetch(
|
|
5324
|
+
`${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`,
|
|
5325
|
+
{
|
|
5326
|
+
headers: {
|
|
5327
|
+
"Content-Type": "application/json",
|
|
5328
|
+
"x-project-id": projectId,
|
|
5329
|
+
"cue-project-id": projectId
|
|
5330
|
+
}
|
|
4223
5331
|
}
|
|
4224
|
-
|
|
5332
|
+
);
|
|
4225
5333
|
if (!response.ok) {
|
|
4226
|
-
throw new Error(
|
|
5334
|
+
throw new Error(
|
|
5335
|
+
`Failed to fetch consumption: ${response.status} ${response.statusText}`
|
|
5336
|
+
);
|
|
4227
5337
|
}
|
|
4228
5338
|
return response.json();
|
|
4229
5339
|
}
|
|
4230
5340
|
};
|
|
4231
5341
|
|
|
4232
5342
|
// libs/js/cue-sdk/src/lib/project.ts
|
|
4233
|
-
var
|
|
4234
|
-
var
|
|
5343
|
+
var import_firestore3 = require("firebase/firestore");
|
|
5344
|
+
var import_functions2 = require("firebase/functions");
|
|
4235
5345
|
var CueProjects = class {
|
|
4236
5346
|
constructor(_auth, app, useEmulator = false, endpoints) {
|
|
4237
5347
|
this._auth = _auth;
|
|
4238
|
-
this._db = (0,
|
|
5348
|
+
this._db = (0, import_firestore3.getFirestore)(app);
|
|
5349
|
+
this._functions = (0, import_functions2.getFunctions)(app, GCP_REGION2);
|
|
5350
|
+
this._gatewayUrl = endpoints?.gatewayUrl ?? "";
|
|
4239
5351
|
if (useEmulator) {
|
|
4240
5352
|
const host = endpoints?.firestoreEmulatorHost ?? "localhost";
|
|
4241
5353
|
const port = endpoints?.firestoreEmulatorPort ?? 8080;
|
|
4242
|
-
(0,
|
|
5354
|
+
(0, import_firestore3.connectFirestoreEmulator)(this._db, host, port);
|
|
5355
|
+
(0, import_functions2.connectFunctionsEmulator)(this._functions, "localhost", 5001);
|
|
4243
5356
|
}
|
|
4244
5357
|
}
|
|
4245
5358
|
_db;
|
|
5359
|
+
_functions;
|
|
5360
|
+
_gatewayUrl;
|
|
4246
5361
|
_requireUser() {
|
|
4247
5362
|
const user = this._auth.currentUser;
|
|
4248
5363
|
if (!user)
|
|
@@ -4254,60 +5369,31 @@ var CueProjects = class {
|
|
|
4254
5369
|
* Throws if a project with the given ID already exists.
|
|
4255
5370
|
*/
|
|
4256
5371
|
async createProject(options) {
|
|
4257
|
-
const
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
throw new Error(`
|
|
4265
|
-
}
|
|
4266
|
-
|
|
4267
|
-
id: projectId,
|
|
4268
|
-
name: options.name,
|
|
4269
|
-
organizationID: options.organizationID,
|
|
4270
|
-
created: now,
|
|
4271
|
-
lastSync: null,
|
|
4272
|
-
isPublic: false,
|
|
4273
|
-
members: [userId],
|
|
4274
|
-
syncers: [userId],
|
|
4275
|
-
admins: [userId],
|
|
4276
|
-
alternativeIDs: [projectId],
|
|
4277
|
-
projectSettings
|
|
4278
|
-
};
|
|
4279
|
-
await (0, import_firestore2.setDoc)(ref5, data);
|
|
4280
|
-
return data;
|
|
5372
|
+
const response = await this._auth.authenticatedFetch(`${this._gatewayUrl}${ENDPOINT_CREATE_PROJECT}`, {
|
|
5373
|
+
method: "POST",
|
|
5374
|
+
headers: { "Content-Type": "application/json" },
|
|
5375
|
+
body: JSON.stringify(options)
|
|
5376
|
+
});
|
|
5377
|
+
if (!response.ok) {
|
|
5378
|
+
const body = await response.text().catch(() => "");
|
|
5379
|
+
throw new Error(`Failed to create project: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`);
|
|
5380
|
+
}
|
|
5381
|
+
return response.json();
|
|
4281
5382
|
}
|
|
4282
5383
|
/**
|
|
4283
|
-
* List all projects where the authenticated user appears in the members
|
|
4284
|
-
*
|
|
5384
|
+
* List all projects where the authenticated user appears in the members array.
|
|
5385
|
+
* Access is gated by Firestore rules which check membership.
|
|
4285
5386
|
*/
|
|
4286
5387
|
async listProjects() {
|
|
4287
5388
|
const userId = this._requireUser();
|
|
4288
|
-
const col = (0,
|
|
4289
|
-
const
|
|
4290
|
-
|
|
4291
|
-
(0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("syncers", "array-contains", userId))),
|
|
4292
|
-
(0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("admins", "array-contains", userId)))
|
|
4293
|
-
]);
|
|
4294
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4295
|
-
const results = [];
|
|
4296
|
-
for (const snap of [memberSnap, syncerSnap, adminSnap]) {
|
|
4297
|
-
for (const d of snap.docs) {
|
|
4298
|
-
const project = d.data();
|
|
4299
|
-
if (!seen.has(project.id)) {
|
|
4300
|
-
seen.add(project.id);
|
|
4301
|
-
results.push(project);
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
}
|
|
4305
|
-
return results;
|
|
5389
|
+
const col = (0, import_firestore3.collection)(this._db, COLLECTION_PROJECTS2);
|
|
5390
|
+
const snap = await (0, import_firestore3.getDocs)((0, import_firestore3.query)(col, (0, import_firestore3.where)("members", "array-contains", userId), (0, import_firestore3.limit)(100)));
|
|
5391
|
+
return snap.docs.map((d) => d.data());
|
|
4306
5392
|
}
|
|
4307
5393
|
/** Fetch a single project by ID. Returns null if not found. */
|
|
4308
5394
|
async getProject(projectId) {
|
|
4309
|
-
const
|
|
4310
|
-
const snap = await (0,
|
|
5395
|
+
const ref6 = (0, import_firestore3.doc)((0, import_firestore3.collection)(this._db, COLLECTION_PROJECTS2), projectId);
|
|
5396
|
+
const snap = await (0, import_firestore3.getDoc)(ref6);
|
|
4311
5397
|
if (!snap.exists())
|
|
4312
5398
|
return null;
|
|
4313
5399
|
return snap.data();
|
|
@@ -4319,28 +5405,49 @@ var CueProjects = class {
|
|
|
4319
5405
|
async incrementUnitsConsumed(projectId, units, userId) {
|
|
4320
5406
|
if (units <= 0)
|
|
4321
5407
|
return;
|
|
4322
|
-
const
|
|
4323
|
-
await (0,
|
|
4324
|
-
unitsConsumed: (0,
|
|
4325
|
-
lastUpdated: (0,
|
|
5408
|
+
const ref6 = (0, import_firestore3.doc)(this._db, "clientSync", projectId);
|
|
5409
|
+
await (0, import_firestore3.setDoc)(ref6, {
|
|
5410
|
+
unitsConsumed: (0, import_firestore3.increment)(units),
|
|
5411
|
+
lastUpdated: (0, import_firestore3.serverTimestamp)(),
|
|
4326
5412
|
lastUserId: userId
|
|
4327
5413
|
}, { merge: true });
|
|
4328
5414
|
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Invite a user to a project by email. Returns the invited user's uid and display name.
|
|
5417
|
+
*/
|
|
5418
|
+
async inviteUserToProject(email, projectId, role) {
|
|
5419
|
+
const fn = (0, import_functions2.httpsCallable)(this._functions, "inviteUserToProject");
|
|
5420
|
+
const res = await fn({ email, spaceId: projectId, role });
|
|
5421
|
+
return res.data;
|
|
5422
|
+
}
|
|
5423
|
+
/** Change an existing member's role on a project. */
|
|
5424
|
+
async changeUserRoleOnProject(uid, projectId, role) {
|
|
5425
|
+
const fn = (0, import_functions2.httpsCallable)(this._functions, "changeUserRoleOnProject");
|
|
5426
|
+
await fn({ uid, spaceId: projectId, role });
|
|
5427
|
+
}
|
|
5428
|
+
/** Remove a member from a project. */
|
|
5429
|
+
async removeUserFromProject(uid, projectId) {
|
|
5430
|
+
const fn = (0, import_functions2.httpsCallable)(this._functions, "removeUserFromProject");
|
|
5431
|
+
await fn({ uid, spaceId: projectId });
|
|
5432
|
+
}
|
|
4329
5433
|
};
|
|
4330
5434
|
|
|
4331
5435
|
// libs/js/cue-sdk/src/lib/profile.ts
|
|
4332
5436
|
var import_auth4 = require("firebase/auth");
|
|
4333
|
-
var
|
|
4334
|
-
var
|
|
5437
|
+
var import_firestore4 = require("firebase/firestore");
|
|
5438
|
+
var import_functions3 = require("firebase/functions");
|
|
4335
5439
|
var CueProfile = class {
|
|
4336
5440
|
constructor(_auth, app, useEmulator, emulatorHost, emulatorPort) {
|
|
4337
5441
|
this._auth = _auth;
|
|
4338
|
-
this._db = (0,
|
|
5442
|
+
this._db = (0, import_firestore4.getFirestore)(app);
|
|
5443
|
+
this._functions = (0, import_functions3.getFunctions)(app, GCP_REGION2);
|
|
4339
5444
|
if (useEmulator) {
|
|
4340
|
-
(0,
|
|
5445
|
+
(0, import_firestore4.connectFirestoreEmulator)(this._db, emulatorHost, emulatorPort);
|
|
5446
|
+
(0, import_functions3.connectFunctionsEmulator)(this._functions, "localhost", 5001);
|
|
4341
5447
|
}
|
|
4342
5448
|
}
|
|
4343
5449
|
_db;
|
|
5450
|
+
_functions;
|
|
4344
5451
|
_apiKeyDocRef;
|
|
4345
5452
|
/** Whether the current user has an active API key. */
|
|
4346
5453
|
async hasAPIKey() {
|
|
@@ -4421,512 +5528,1197 @@ var CueProfile = class {
|
|
|
4421
5528
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4422
5529
|
const randomKey = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(150)), (b) => alphabet[b & 63]).join("");
|
|
4423
5530
|
const data = { key: `cue-${randomKey}`, uid, expiration };
|
|
4424
|
-
const col = (0,
|
|
4425
|
-
this._apiKeyDocRef = await (0,
|
|
5531
|
+
const col = (0, import_firestore4.collection)(this._db, COLLECTION_API_KEYS2);
|
|
5532
|
+
this._apiKeyDocRef = await (0, import_firestore4.addDoc)(col, data);
|
|
4426
5533
|
return data;
|
|
4427
5534
|
}
|
|
4428
5535
|
/** Revokes the current user's API key. */
|
|
4429
5536
|
async revokeAPIKey() {
|
|
4430
5537
|
if (!this._apiKeyDocRef)
|
|
4431
5538
|
throw new Error("No API key document reference");
|
|
4432
|
-
await (0,
|
|
5539
|
+
await (0, import_firestore4.deleteDoc)(this._apiKeyDocRef);
|
|
4433
5540
|
this._apiKeyDocRef = void 0;
|
|
4434
5541
|
}
|
|
4435
5542
|
/** Fetches the current user's existing API key. */
|
|
4436
5543
|
async requestAPIKey() {
|
|
4437
5544
|
if (!this._apiKeyDocRef)
|
|
4438
5545
|
throw new Error("No API key document reference");
|
|
4439
|
-
const
|
|
4440
|
-
return { key:
|
|
5546
|
+
const doc3 = (await (0, import_firestore4.getDoc)(this._apiKeyDocRef)).data();
|
|
5547
|
+
return { key: doc3.key, expiration: doc3.expiration };
|
|
4441
5548
|
}
|
|
4442
5549
|
async _checkIfUserHasAPIKey(uid) {
|
|
4443
|
-
const col = (0,
|
|
4444
|
-
const q = (0,
|
|
4445
|
-
const snapshot = await (0,
|
|
5550
|
+
const col = (0, import_firestore4.collection)(this._db, COLLECTION_API_KEYS2);
|
|
5551
|
+
const q = (0, import_firestore4.query)(col, (0, import_firestore4.where)("uid", "==", uid), (0, import_firestore4.limit)(1));
|
|
5552
|
+
const snapshot = await (0, import_firestore4.getDocs)(q);
|
|
4446
5553
|
if (!snapshot.empty)
|
|
4447
5554
|
this._apiKeyDocRef = snapshot.docs[0].ref;
|
|
4448
5555
|
return !snapshot.empty;
|
|
4449
5556
|
}
|
|
5557
|
+
/** Returns all organizations the current user is a member of. */
|
|
5558
|
+
async listOrganizations() {
|
|
5559
|
+
const uid = this._auth.currentUser?.uid;
|
|
5560
|
+
if (!uid)
|
|
5561
|
+
throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
5562
|
+
const col = (0, import_firestore4.collection)(this._db, COLLECTION_ORGANIZATIONS2);
|
|
5563
|
+
const q = (0, import_firestore4.query)(col, (0, import_firestore4.where)("members", "array-contains", uid));
|
|
5564
|
+
const snap = await (0, import_firestore4.getDocs)(q);
|
|
5565
|
+
return snap.docs.map((d) => d.data());
|
|
5566
|
+
}
|
|
4450
5567
|
_requireUser() {
|
|
4451
5568
|
const user = this._auth.currentUser;
|
|
4452
5569
|
if (!user)
|
|
4453
5570
|
throw new Error("Not authenticated");
|
|
4454
5571
|
return user;
|
|
4455
5572
|
}
|
|
5573
|
+
/**
|
|
5574
|
+
* Fetch display name and email for a list of user UIDs.
|
|
5575
|
+
* Uses the `getUserInfo` Firebase callable function.
|
|
5576
|
+
*/
|
|
5577
|
+
async getUserInfo(uids) {
|
|
5578
|
+
const fn = (0, import_functions3.httpsCallable)(this._functions, "getUserInfo");
|
|
5579
|
+
const res = await fn({ uids });
|
|
5580
|
+
return res.data;
|
|
5581
|
+
}
|
|
5582
|
+
/** Record that the current user has accepted the terms of service. */
|
|
5583
|
+
async acceptTerms() {
|
|
5584
|
+
const fn = (0, import_functions3.httpsCallable)(this._functions, "acceptTerms");
|
|
5585
|
+
await fn({});
|
|
5586
|
+
}
|
|
4456
5587
|
};
|
|
4457
5588
|
|
|
4458
|
-
// libs/js/cue-sdk/src/lib/
|
|
4459
|
-
var
|
|
4460
|
-
var
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
authEmulatorUrl: "http://localhost:9099",
|
|
4475
|
-
storageEmulatorHost: "localhost",
|
|
4476
|
-
storageEmulatorPort: 9199,
|
|
4477
|
-
firestoreEmulatorHost: "localhost",
|
|
4478
|
-
firestoreEmulatorPort: 8080
|
|
4479
|
-
}
|
|
5589
|
+
// libs/js/cue-sdk/src/lib/privileges.ts
|
|
5590
|
+
var ROLE_HIERARCHY = ["superadmin", "admin", "syncer", "member"];
|
|
5591
|
+
var REQUIRED_ROLES = {
|
|
5592
|
+
createEntities: "admin",
|
|
5593
|
+
createProvider: "superadmin",
|
|
5594
|
+
changeContentCategories: "syncer",
|
|
5595
|
+
deleteDocuments: "superadmin",
|
|
5596
|
+
deleteUserFromProject: "admin",
|
|
5597
|
+
downloadDocuments: "member",
|
|
5598
|
+
editContentCategories: "syncer",
|
|
5599
|
+
editPublicReposAvailableToAgent: "admin",
|
|
5600
|
+
editTier: "admin",
|
|
5601
|
+
inviteUserToProject: "member",
|
|
5602
|
+
renameDocuments: "superadmin",
|
|
5603
|
+
uploadDocuments: "syncer",
|
|
5604
|
+
viewEntities: "member"
|
|
4480
5605
|
};
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
this.
|
|
4501
|
-
|
|
4502
|
-
this.
|
|
4503
|
-
|
|
4504
|
-
this.
|
|
4505
|
-
this._app,
|
|
4506
|
-
this._isEmulator,
|
|
4507
|
-
this._endpoints.firestoreEmulatorHost,
|
|
4508
|
-
this._endpoints.firestoreEmulatorPort
|
|
5606
|
+
function defaultPrivileges() {
|
|
5607
|
+
return {
|
|
5608
|
+
changeContentCategories: false,
|
|
5609
|
+
createEntities: false,
|
|
5610
|
+
createProvider: false,
|
|
5611
|
+
deleteDocuments: false,
|
|
5612
|
+
deleteUserFromProject: false,
|
|
5613
|
+
downloadDocuments: false,
|
|
5614
|
+
editContentCategories: false,
|
|
5615
|
+
editPublicReposAvailableToAgent: false,
|
|
5616
|
+
editTier: false,
|
|
5617
|
+
inviteUserToProject: false,
|
|
5618
|
+
renameDocuments: false,
|
|
5619
|
+
uploadDocuments: false,
|
|
5620
|
+
viewEntities: false
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
var CuePrivileges = class {
|
|
5624
|
+
constructor(_isSuperAdmin) {
|
|
5625
|
+
this._isSuperAdmin = _isSuperAdmin;
|
|
5626
|
+
this._projectRoles = new CueSignal([]);
|
|
5627
|
+
this.privileges = cueComputed(
|
|
5628
|
+
[this._projectRoles, _isSuperAdmin],
|
|
5629
|
+
() => this._compute()
|
|
4509
5630
|
);
|
|
4510
5631
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
5632
|
+
_projectRoles;
|
|
5633
|
+
/**
|
|
5634
|
+
* Reactive signal — current user's privileges for the selected project.
|
|
5635
|
+
* Recomputes automatically when `setProjectRoles()` is called or when
|
|
5636
|
+
* the `isSuperAdmin` signal changes.
|
|
5637
|
+
*/
|
|
5638
|
+
privileges;
|
|
5639
|
+
/**
|
|
5640
|
+
* Set the user's roles for the currently selected project.
|
|
5641
|
+
*
|
|
5642
|
+
* Roles are expanded along the hierarchy: `admin` automatically includes
|
|
5643
|
+
* `syncer` and `member`; `syncer` includes `member`. Pass an empty array
|
|
5644
|
+
* to reset to the lowest privilege level.
|
|
5645
|
+
*/
|
|
5646
|
+
setProjectRoles(roles) {
|
|
5647
|
+
const expanded = this._expand(roles);
|
|
5648
|
+
const current = this._projectRoles.get();
|
|
5649
|
+
const same = current.length === expanded.length && current.every((r, i) => r === expanded[i]);
|
|
5650
|
+
if (!same) {
|
|
5651
|
+
this._projectRoles.set(expanded);
|
|
5652
|
+
}
|
|
4514
5653
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
5654
|
+
_expand(roles) {
|
|
5655
|
+
if (this._isSuperAdmin.get()) {
|
|
5656
|
+
return ["superadmin", "admin", "syncer", "member"];
|
|
5657
|
+
}
|
|
5658
|
+
const highestIdx = ROLE_HIERARCHY.findIndex((r) => roles.includes(r));
|
|
5659
|
+
if (highestIdx === -1)
|
|
5660
|
+
return [];
|
|
5661
|
+
return Array.from(ROLE_HIERARCHY.slice(highestIdx));
|
|
5662
|
+
}
|
|
5663
|
+
_compute() {
|
|
5664
|
+
const roles = this._projectRoles.get();
|
|
5665
|
+
const result = defaultPrivileges();
|
|
5666
|
+
for (const key of Object.keys(REQUIRED_ROLES)) {
|
|
5667
|
+
result[key] = roles.includes(REQUIRED_ROLES[key]);
|
|
5668
|
+
}
|
|
5669
|
+
return result;
|
|
4518
5670
|
}
|
|
4519
5671
|
};
|
|
4520
5672
|
|
|
4521
|
-
// libs/js/cue-sdk/src/lib/
|
|
5673
|
+
// libs/js/cue-sdk/src/lib/cache.ts
|
|
4522
5674
|
var import_storage4 = require("firebase/storage");
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
queryEndpoint;
|
|
4527
|
-
updateEndpoint;
|
|
4528
|
-
baseHeaders;
|
|
4529
|
-
static RELEVANT_HEADER_KEYS = [
|
|
4530
|
-
"authorization",
|
|
4531
|
-
"Authorization",
|
|
4532
|
-
"x-project-id"
|
|
4533
|
-
];
|
|
4534
|
-
constructor(graphOptions) {
|
|
4535
|
-
this.queryEndpoint = graphOptions.queryEndpoint;
|
|
4536
|
-
this.updateEndpoint = graphOptions.updateEndpoint;
|
|
4537
|
-
this.baseHeaders = Object.fromEntries(
|
|
4538
|
-
Object.entries(graphOptions.originalHeaders || {}).filter(
|
|
4539
|
-
([key]) => _Fuseki.RELEVANT_HEADER_KEYS.includes(key)
|
|
4540
|
-
)
|
|
4541
|
-
);
|
|
4542
|
-
if (graphOptions.authHeader !== void 0) {
|
|
4543
|
-
this.baseHeaders["Authorization"] = graphOptions.authHeader;
|
|
4544
|
-
}
|
|
5675
|
+
var CueCache = class {
|
|
5676
|
+
constructor(_storage) {
|
|
5677
|
+
this._storage = _storage;
|
|
4545
5678
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
return res.boolean;
|
|
5679
|
+
// ── Query cache ────────────────────────────────────────────────────────────
|
|
5680
|
+
async getQueryCache(projectId, id) {
|
|
5681
|
+
return this._get(`portal/projects/${projectId}/queries`, id);
|
|
4550
5682
|
}
|
|
4551
|
-
async
|
|
4552
|
-
|
|
5683
|
+
async setQueryCache(projectId, id, payload) {
|
|
5684
|
+
return this._set(`portal/projects/${projectId}/queries`, id, payload);
|
|
5685
|
+
}
|
|
5686
|
+
// ── Agent session cache ────────────────────────────────────────────────────
|
|
5687
|
+
async getAgentSessionCache(projectId, id) {
|
|
5688
|
+
return this._get(`portal/projects/${projectId}/agent`, id);
|
|
5689
|
+
}
|
|
5690
|
+
async setAgentSessionCache(projectId, id, payload) {
|
|
5691
|
+
return this._set(`portal/projects/${projectId}/agent`, id, payload);
|
|
5692
|
+
}
|
|
5693
|
+
// ── User-project cache ─────────────────────────────────────────────────────
|
|
5694
|
+
async getUserProjectCache(projectId, userId, id) {
|
|
5695
|
+
return this._get(`portal/projects/${projectId}/users/${userId}`, id);
|
|
5696
|
+
}
|
|
5697
|
+
async setUserProjectCache(projectId, userId, id, payload) {
|
|
5698
|
+
return this._set(
|
|
5699
|
+
`portal/projects/${projectId}/users/${userId}`,
|
|
5700
|
+
id,
|
|
5701
|
+
payload
|
|
5702
|
+
);
|
|
5703
|
+
}
|
|
5704
|
+
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
5705
|
+
async _get(basePath, id) {
|
|
4553
5706
|
try {
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
5707
|
+
const blob = await (0, import_storage4.getBlob)((0, import_storage4.ref)(this._storage, `${basePath}/${id}.json.gz`));
|
|
5708
|
+
const compressed = await blob.arrayBuffer();
|
|
5709
|
+
const ds = new DecompressionStream("gzip");
|
|
5710
|
+
const writer = ds.writable.getWriter();
|
|
5711
|
+
const reader = ds.readable.getReader();
|
|
5712
|
+
writer.write(new Uint8Array(compressed));
|
|
5713
|
+
writer.close();
|
|
5714
|
+
const chunks = [];
|
|
5715
|
+
let result = await reader.read();
|
|
5716
|
+
while (!result.done) {
|
|
5717
|
+
chunks.push(result.value);
|
|
5718
|
+
result = await reader.read();
|
|
5719
|
+
}
|
|
5720
|
+
const totalLength = chunks.reduce((s, c) => s + c.length, 0);
|
|
5721
|
+
const out = new Uint8Array(totalLength);
|
|
5722
|
+
let offset = 0;
|
|
5723
|
+
for (const chunk of chunks) {
|
|
5724
|
+
out.set(chunk, offset);
|
|
5725
|
+
offset += chunk.length;
|
|
5726
|
+
}
|
|
5727
|
+
return JSON.parse(new TextDecoder().decode(out));
|
|
4563
5728
|
} catch (err) {
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
5729
|
+
if (_isNotFound(err))
|
|
5730
|
+
return void 0;
|
|
5731
|
+
throw err;
|
|
4567
5732
|
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
5733
|
+
}
|
|
5734
|
+
async _set(basePath, id, payload) {
|
|
5735
|
+
const input = new TextEncoder().encode(JSON.stringify(payload));
|
|
5736
|
+
const cs = new CompressionStream("gzip");
|
|
5737
|
+
const writer = cs.writable.getWriter();
|
|
5738
|
+
const reader = cs.readable.getReader();
|
|
5739
|
+
writer.write(new Uint8Array(input));
|
|
5740
|
+
writer.close();
|
|
5741
|
+
const chunks = [];
|
|
5742
|
+
let result = await reader.read();
|
|
5743
|
+
while (!result.done) {
|
|
5744
|
+
chunks.push(result.value);
|
|
5745
|
+
result = await reader.read();
|
|
4571
5746
|
}
|
|
4572
|
-
|
|
5747
|
+
const totalLength = chunks.reduce((s, c) => s + c.length, 0);
|
|
5748
|
+
const buffer = new Uint8Array(totalLength);
|
|
5749
|
+
let offset = 0;
|
|
5750
|
+
for (const chunk of chunks) {
|
|
5751
|
+
buffer.set(chunk, offset);
|
|
5752
|
+
offset += chunk.length;
|
|
5753
|
+
}
|
|
5754
|
+
await (0, import_storage4.uploadBytes)(
|
|
5755
|
+
(0, import_storage4.ref)(this._storage, `${basePath}/${id}.json.gz`),
|
|
5756
|
+
buffer,
|
|
5757
|
+
{ contentType: "application/gzip" }
|
|
5758
|
+
);
|
|
4573
5759
|
}
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
5760
|
+
};
|
|
5761
|
+
function _isNotFound(err) {
|
|
5762
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "storage/object-not-found";
|
|
5763
|
+
}
|
|
5764
|
+
|
|
5765
|
+
// libs/js/cue-sdk/src/lib/schema.ts
|
|
5766
|
+
var CueProjectSchema = class {
|
|
5767
|
+
constructor(_api, _projectId, language, _queryCache, _graphType) {
|
|
5768
|
+
this._api = _api;
|
|
5769
|
+
this._projectId = _projectId;
|
|
5770
|
+
this._queryCache = _queryCache;
|
|
5771
|
+
this._graphType = _graphType;
|
|
5772
|
+
this._language = new CueSignal(language);
|
|
5773
|
+
this._contentCategories = new CueSignal([]);
|
|
5774
|
+
this._entityCategories = new CueSignal([]);
|
|
5775
|
+
this._relationships = new CueSignal([]);
|
|
5776
|
+
this.availableContentCategories = this._contentCategories.asReadonly();
|
|
5777
|
+
this.availableEntityCategories = this._entityCategories.asReadonly();
|
|
5778
|
+
this.availableEntityRelationships = this._relationships.asReadonly();
|
|
5779
|
+
this._load(language).catch(
|
|
5780
|
+
(err) => console.error("[CueProjectSchema] Initial load failed:", err)
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5783
|
+
_cache = /* @__PURE__ */ new Map();
|
|
5784
|
+
_language;
|
|
5785
|
+
_contentCategories;
|
|
5786
|
+
_entityCategories;
|
|
5787
|
+
_relationships;
|
|
5788
|
+
/** Currently active content categories for the selected language. */
|
|
5789
|
+
availableContentCategories;
|
|
5790
|
+
/** Currently active entity categories for the selected language. */
|
|
5791
|
+
availableEntityCategories;
|
|
5792
|
+
/** Currently active entity relationship types for the selected language. */
|
|
5793
|
+
availableEntityRelationships;
|
|
5794
|
+
/** Returns the currently active language. */
|
|
5795
|
+
get language() {
|
|
5796
|
+
return this._language.get();
|
|
5797
|
+
}
|
|
5798
|
+
/**
|
|
5799
|
+
* Switch the active language. If the data for this language has already been
|
|
5800
|
+
* fetched it is applied immediately from cache; otherwise a new SPARQL fetch
|
|
5801
|
+
* is triggered.
|
|
5802
|
+
*/
|
|
5803
|
+
setLanguage(lang) {
|
|
5804
|
+
if (this._language.get() === lang)
|
|
5805
|
+
return;
|
|
5806
|
+
this._language.set(lang);
|
|
5807
|
+
this._load(lang).catch(
|
|
5808
|
+
(err) => console.error("[CueProjectSchema] Language switch failed:", err)
|
|
5809
|
+
);
|
|
5810
|
+
}
|
|
5811
|
+
/**
|
|
5812
|
+
* Force a re-fetch for the current language, bypassing the cache.
|
|
5813
|
+
* Useful when the triplestore data has changed.
|
|
5814
|
+
*/
|
|
5815
|
+
async refresh() {
|
|
5816
|
+
const lang = this._language.get();
|
|
5817
|
+
this._cache.delete(lang);
|
|
5818
|
+
await this._load(lang);
|
|
5819
|
+
}
|
|
5820
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
5821
|
+
async _load(lang) {
|
|
5822
|
+
const memCached = this._cache.get(lang);
|
|
5823
|
+
if (memCached) {
|
|
5824
|
+
this._apply(memCached);
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
const qContent = this._buildCategoriesQuery(lang, "ContentCategory");
|
|
5828
|
+
const qEntity = this._buildCategoriesQuery(lang, "EntityCategory");
|
|
5829
|
+
const qRels = this._buildRelationshipsQuery(lang);
|
|
5830
|
+
await staleWhileRevalidate(
|
|
5831
|
+
qContent + qEntity + qRels,
|
|
5832
|
+
async () => {
|
|
5833
|
+
const [contentCategories, entityCategories, relationships] = await Promise.all([
|
|
5834
|
+
this._runCategoriesQuery(qContent),
|
|
5835
|
+
this._runCategoriesQuery(qEntity),
|
|
5836
|
+
this._runRelationshipsQuery(qRels)
|
|
5837
|
+
]);
|
|
5838
|
+
return { contentCategories, entityCategories, relationships };
|
|
4581
5839
|
},
|
|
4582
|
-
|
|
4583
|
-
|
|
5840
|
+
(snapshot) => {
|
|
5841
|
+
this._cache.set(lang, snapshot);
|
|
5842
|
+
if (this._language.get() === lang)
|
|
5843
|
+
this._apply(snapshot);
|
|
5844
|
+
},
|
|
5845
|
+
this._queryCache
|
|
5846
|
+
);
|
|
5847
|
+
}
|
|
5848
|
+
_apply(snapshot) {
|
|
5849
|
+
this._contentCategories.set(snapshot.contentCategories);
|
|
5850
|
+
this._entityCategories.set(snapshot.entityCategories);
|
|
5851
|
+
this._relationships.set(snapshot.relationships);
|
|
5852
|
+
}
|
|
5853
|
+
async _fetchCategories(lang, type) {
|
|
5854
|
+
return this._runCategoriesQuery(this._buildCategoriesQuery(lang, type));
|
|
5855
|
+
}
|
|
5856
|
+
_buildCategoriesQuery(lang, type) {
|
|
5857
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
5858
|
+
PREFIX skos: <${prefixCC["skos"]}>
|
|
5859
|
+
SELECT ?iri ?parent (SAMPLE(?l) AS ?label)
|
|
5860
|
+
WHERE {
|
|
5861
|
+
?iri a qcy:${type} .
|
|
5862
|
+
OPTIONAL { ?iri skos:prefLabel ?lang_label FILTER(LANG(?lang_label) = "${lang}") }
|
|
5863
|
+
OPTIONAL { ?iri skos:prefLabel ?no_lang_label }
|
|
5864
|
+
BIND(COALESCE(?lang_label, ?no_lang_label) AS ?l)
|
|
5865
|
+
OPTIONAL { ?iri skos:broader ?parent }
|
|
5866
|
+
}
|
|
5867
|
+
GROUP BY ?iri ?parent`;
|
|
5868
|
+
}
|
|
5869
|
+
async _runCategoriesQuery(query4) {
|
|
5870
|
+
const data = await this._api.sparql(query4, this._projectId, this._graphType);
|
|
5871
|
+
return data.results.bindings.filter((b) => b["iri"] !== void 0).map((b) => {
|
|
5872
|
+
const iri = b["iri"].value;
|
|
5873
|
+
return {
|
|
5874
|
+
iri,
|
|
5875
|
+
label: b["label"]?.value ?? iri.split("#").at(-1) ?? iri,
|
|
5876
|
+
parent: b["parent"]?.value
|
|
5877
|
+
};
|
|
4584
5878
|
});
|
|
4585
|
-
if (accept === "application/ld+json") {
|
|
4586
|
-
return await res.json();
|
|
4587
|
-
}
|
|
4588
|
-
return await res.text();
|
|
4589
5879
|
}
|
|
4590
|
-
async
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
5880
|
+
async _fetchRelationships(lang) {
|
|
5881
|
+
return this._runRelationshipsQuery(this._buildRelationshipsQuery(lang));
|
|
5882
|
+
}
|
|
5883
|
+
_buildRelationshipsQuery(lang) {
|
|
5884
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
5885
|
+
PREFIX rdfs: <${prefixCC["rdfs"]}>
|
|
5886
|
+
SELECT ?iri ?parent (SAMPLE(?l) AS ?label)
|
|
5887
|
+
WHERE {
|
|
5888
|
+
?x qcy:relatedEntity ?y ;
|
|
5889
|
+
?iri ?y .
|
|
5890
|
+
OPTIONAL { ?iri rdfs:label ?lang_label FILTER(LANG(?lang_label) = "${lang}") }
|
|
5891
|
+
OPTIONAL { ?iri rdfs:label ?no_lang_label }
|
|
5892
|
+
BIND(COALESCE(?lang_label, ?no_lang_label) AS ?l)
|
|
5893
|
+
OPTIONAL { ?iri rdfs:subPropertyOf ?parent }
|
|
5894
|
+
}
|
|
5895
|
+
GROUP BY ?iri ?parent`;
|
|
5896
|
+
}
|
|
5897
|
+
async _runRelationshipsQuery(query4) {
|
|
5898
|
+
const data = await this._api.sparql(query4, this._projectId, this._graphType);
|
|
5899
|
+
return data.results.bindings.filter((b) => b["iri"] !== void 0).map((b) => {
|
|
5900
|
+
const iri = b["iri"].value;
|
|
5901
|
+
return {
|
|
5902
|
+
iri,
|
|
5903
|
+
label: b["label"]?.value ?? iri.split("#").at(-1) ?? iri,
|
|
5904
|
+
parent: b["parent"]?.value
|
|
5905
|
+
};
|
|
4598
5906
|
});
|
|
4599
|
-
if (!res.ok) {
|
|
4600
|
-
const body = await res.text();
|
|
4601
|
-
throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
|
|
4602
|
-
}
|
|
4603
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
4604
|
-
if (contentType.includes("application/json")) {
|
|
4605
|
-
return await res.json();
|
|
4606
|
-
}
|
|
4607
|
-
return {
|
|
4608
|
-
ok: res.ok,
|
|
4609
|
-
status: res.status,
|
|
4610
|
-
message: await res.text()
|
|
4611
|
-
};
|
|
4612
5907
|
}
|
|
4613
5908
|
};
|
|
4614
5909
|
|
|
4615
|
-
// libs/js/
|
|
4616
|
-
var
|
|
4617
|
-
var
|
|
4618
|
-
var
|
|
4619
|
-
constructor(
|
|
4620
|
-
|
|
4621
|
-
this.
|
|
5910
|
+
// libs/js/cue-sdk/src/lib/entities.ts
|
|
5911
|
+
var OSM_ENDPOINT = "https://qlever.dev/api/osm-planet";
|
|
5912
|
+
var EXCLUDE_OSM_RELATION = true;
|
|
5913
|
+
var CueProjectEntities = class {
|
|
5914
|
+
constructor(_api, _projectId, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
5915
|
+
this._api = _api;
|
|
5916
|
+
this._projectId = _projectId;
|
|
5917
|
+
this._queryCache = _queryCache;
|
|
5918
|
+
this._graphType = _graphType;
|
|
5919
|
+
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
5920
|
+
this.entityInfoMap = this._entityInfoMapComputed;
|
|
5921
|
+
this.entityGraph = this._entityGraph.asReadonly();
|
|
5922
|
+
this._entityOSMMap.subscribe(() => this._checkPendingOSMFetches());
|
|
5923
|
+
this._fetchEntityGraph().catch(
|
|
5924
|
+
(err) => console.error("[CueProjectEntities] Entity graph fetch failed:", err)
|
|
5925
|
+
);
|
|
5926
|
+
}
|
|
5927
|
+
/** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
|
|
5928
|
+
baseURL;
|
|
5929
|
+
// ── Internal writable slices ───────────────────────────────────────────────
|
|
5930
|
+
_entityDetails = new CueSignal({});
|
|
5931
|
+
_entityDocuments = new CueSignal({});
|
|
5932
|
+
_entityRelationships = new CueSignal({});
|
|
5933
|
+
_entityOSMMap = new CueSignal({});
|
|
5934
|
+
_osmWKTMap = new CueSignal({});
|
|
5935
|
+
_fetchingOSMIds = /* @__PURE__ */ new Set();
|
|
5936
|
+
_entityGraph = new CueSignal(void 0);
|
|
5937
|
+
// ── Derived signals ────────────────────────────────────────────────────────
|
|
5938
|
+
_entityInfoMapComputed = cueComputed(
|
|
5939
|
+
[
|
|
5940
|
+
this._entityDetails,
|
|
5941
|
+
this._entityDocuments,
|
|
5942
|
+
this._entityRelationships,
|
|
5943
|
+
this._entityOSMMap,
|
|
5944
|
+
this._osmWKTMap
|
|
5945
|
+
],
|
|
5946
|
+
() => this._computeEntityInfoMap()
|
|
5947
|
+
);
|
|
5948
|
+
/** Merged per-entity detail map. Updated reactively as data arrives. */
|
|
5949
|
+
entityInfoMap;
|
|
5950
|
+
/** Project-level category graph (fetched once per project). */
|
|
5951
|
+
entityGraph;
|
|
5952
|
+
// ── Public helpers ─────────────────────────────────────────────────────────
|
|
5953
|
+
/**
|
|
5954
|
+
* Constructs the full RDF IRI for the given entity UUID.
|
|
5955
|
+
* Use this to bridge the UUID-based batch APIs and the IRI-based per-entity APIs.
|
|
5956
|
+
*/
|
|
5957
|
+
entityIri(uuid) {
|
|
5958
|
+
return `${this.baseURL}${uuid}`;
|
|
5959
|
+
}
|
|
5960
|
+
/**
|
|
5961
|
+
* Resets all entity state and re-fetches the entity graph.
|
|
5962
|
+
* Call when the active project changes.
|
|
5963
|
+
*/
|
|
5964
|
+
reset() {
|
|
5965
|
+
this._entityDetails.set({});
|
|
5966
|
+
this._entityDocuments.set({});
|
|
5967
|
+
this._entityRelationships.set({});
|
|
5968
|
+
this._entityOSMMap.set({});
|
|
5969
|
+
this._osmWKTMap.set({});
|
|
5970
|
+
this._entityGraph.set(void 0);
|
|
5971
|
+
this._fetchingOSMIds.clear();
|
|
5972
|
+
this._fetchEntityGraph().catch(
|
|
5973
|
+
(err) => console.error("[CueProjectEntities] Entity graph fetch failed after reset:", err)
|
|
5974
|
+
);
|
|
5975
|
+
}
|
|
5976
|
+
// ── Public imperative API ──────────────────────────────────────────────────
|
|
5977
|
+
/**
|
|
5978
|
+
* Lazily batch-fetches core data (label + categories) for the given entity
|
|
5979
|
+
* UUIDs. Already-fetched UUIDs are skipped.
|
|
5980
|
+
*
|
|
5981
|
+
* Data is merged into `entityInfoMap` once the SPARQL response arrives.
|
|
5982
|
+
*/
|
|
5983
|
+
requestEntityData(uuids, includeMentionCount = false) {
|
|
5984
|
+
const newUUIDs = uuids.filter((id) => this._entityDetails.get()[id] === void 0);
|
|
5985
|
+
if (newUUIDs.length === 0)
|
|
5986
|
+
return;
|
|
5987
|
+
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
5988
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
5989
|
+
PREFIX r: <${this.baseURL}>
|
|
5990
|
+
SELECT ?id (SAMPLE(?val) AS ?value) (GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories) ?mentionCount
|
|
5991
|
+
WHERE {
|
|
5992
|
+
VALUES ?iri { ${values} }
|
|
5993
|
+
?iri qcy:hasEntityCategory ?cat ;
|
|
5994
|
+
qcy:value ?val
|
|
5995
|
+
BIND(REPLACE(STR(?iri), "^.*/([^/]*)$", "$1") AS ?id)
|
|
5996
|
+
${includeMentionCount ? `{SELECT ?iri (COUNT(?mention) AS ?mentionCount)
|
|
5997
|
+
WHERE { ?mention qcy:resolvesTo ?iri } GROUP BY ?iri}` : ""}
|
|
5998
|
+
}
|
|
5999
|
+
GROUP BY ?id ?mentionCount`;
|
|
6000
|
+
this._api.sparql(q, this._projectId, this._graphType).then((data) => {
|
|
6001
|
+
const result = data;
|
|
6002
|
+
const updates = { ...this._entityDetails.get() };
|
|
6003
|
+
result.results.bindings.forEach((b) => {
|
|
6004
|
+
if (!b["id"])
|
|
6005
|
+
return;
|
|
6006
|
+
const id = b["id"].value;
|
|
6007
|
+
updates[id] = {
|
|
6008
|
+
value: b["value"]?.value ?? "",
|
|
6009
|
+
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
|
|
6010
|
+
mentionCount: b["mentionCount"] ? parseInt(b["mentionCount"].value, 10) : void 0
|
|
6011
|
+
};
|
|
6012
|
+
});
|
|
6013
|
+
this._entityDetails.set(updates);
|
|
6014
|
+
}).catch(
|
|
6015
|
+
(err) => console.error("[CueProjectEntities] requestEntityData failed:", err)
|
|
6016
|
+
);
|
|
4622
6017
|
}
|
|
4623
|
-
};
|
|
4624
|
-
var QLever = class _QLever {
|
|
4625
|
-
queryEndpoint;
|
|
4626
|
-
updateEndpoint;
|
|
4627
|
-
dataEndpoint;
|
|
4628
|
-
baseHeaders;
|
|
4629
|
-
static RELEVANT_HEADER_KEYS = [
|
|
4630
|
-
"authorization",
|
|
4631
|
-
"Authorization",
|
|
4632
|
-
"x-project-id",
|
|
4633
|
-
"x-user-roles"
|
|
4634
|
-
// add more if needed
|
|
4635
|
-
];
|
|
4636
|
-
/** Max retries on 423 Locked (rebuild in progress). */
|
|
4637
|
-
static LOCKED_MAX_RETRIES = parseInt(process.env["QLEVER_LOCKED_MAX_RETRIES"] ?? "10", 10);
|
|
4638
|
-
/** Base delay (ms) for exponential backoff on 423. */
|
|
4639
|
-
static LOCKED_BASE_DELAY_MS = parseInt(process.env["QLEVER_LOCKED_BASE_DELAY_MS"] ?? "2000", 10);
|
|
4640
6018
|
/**
|
|
4641
|
-
*
|
|
4642
|
-
*
|
|
6019
|
+
* Lazily fetches OSM location data for the given entity UUIDs.
|
|
6020
|
+
* Already-fetched UUIDs are skipped.
|
|
6021
|
+
*
|
|
6022
|
+
* OSM WKT geometry is fetched in a second pass via a federated SPARQL SERVICE
|
|
6023
|
+
* query and merged into `entityInfoMap` reactively once it arrives.
|
|
4643
6024
|
*/
|
|
4644
|
-
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
6025
|
+
async requestEntityLocations(uuids) {
|
|
6026
|
+
const newUUIDs = uuids.filter((id) => this._entityOSMMap.get()[id] === void 0);
|
|
6027
|
+
if (newUUIDs.length === 0)
|
|
6028
|
+
return;
|
|
6029
|
+
const osmInit = { ...this._entityOSMMap.get() };
|
|
6030
|
+
for (const id of newUUIDs)
|
|
6031
|
+
osmInit[id] = { direct: [], indirect: [] };
|
|
6032
|
+
this._entityOSMMap.set(osmInit);
|
|
6033
|
+
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
6034
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6035
|
+
PREFIX r: <${this.baseURL}>
|
|
6036
|
+
SELECT ?id ?osm ?direct ?rels ?entityUUID
|
|
6037
|
+
WHERE {
|
|
6038
|
+
VALUES ?iri { ${values} }
|
|
6039
|
+
{
|
|
6040
|
+
SELECT ?iri ?osm ?direct
|
|
6041
|
+
WHERE {
|
|
6042
|
+
?iri qcy:similarTo ?osm .
|
|
6043
|
+
FILTER STRSTARTS(STR(?osm), "https://www.openstreetmap.org/")
|
|
6044
|
+
BIND(true AS ?direct)
|
|
4661
6045
|
}
|
|
4662
|
-
throw lastError;
|
|
4663
6046
|
}
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
)
|
|
4672
|
-
|
|
4673
|
-
|
|
6047
|
+
UNION
|
|
6048
|
+
{
|
|
6049
|
+
SELECT ?iri ?osm ?direct ?entityUUID (GROUP_CONCAT(STR(?rel); SEPARATOR=";") AS ?rels)
|
|
6050
|
+
WHERE {
|
|
6051
|
+
?iri qcy:relatedEntity ?loc .
|
|
6052
|
+
?iri ?rel ?loc .
|
|
6053
|
+
?loc qcy:similarTo ?osm .
|
|
6054
|
+
FILTER STRSTARTS(STR(?osm), "https://www.openstreetmap.org/")
|
|
6055
|
+
FILTER(?rel != qcy:relatedEntity)
|
|
6056
|
+
BIND(false AS ?direct)
|
|
6057
|
+
BIND(REPLACE(STR(?loc), "^.*/([^/]*)$", "$1") AS ?entityUUID)
|
|
6058
|
+
} GROUP BY ?iri ?osm ?direct ?entityUUID
|
|
6059
|
+
}
|
|
6060
|
+
BIND(REPLACE(STR(?iri), "^.*/([^/]*)$", "$1") AS ?id)
|
|
6061
|
+
}`;
|
|
6062
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6063
|
+
const update = { ...this._entityOSMMap.get() };
|
|
6064
|
+
data.results.bindings.forEach((b) => {
|
|
6065
|
+
if (!b["id"] || !b["osm"])
|
|
6066
|
+
return;
|
|
6067
|
+
const id = b["id"].value;
|
|
6068
|
+
const osm = b["osm"].value;
|
|
6069
|
+
const direct = b["direct"]?.value === "true";
|
|
6070
|
+
const rels = b["rels"]?.value?.split(";").filter(Boolean) ?? [];
|
|
6071
|
+
const entityUUID = b["entityUUID"]?.value;
|
|
6072
|
+
const entry = update[id] ?? { direct: [], indirect: [] };
|
|
6073
|
+
if (direct) {
|
|
6074
|
+
entry.direct.push(osm);
|
|
6075
|
+
} else if (entityUUID) {
|
|
6076
|
+
entry.indirect.push({ osm, viaRels: rels, entityUUID });
|
|
6077
|
+
}
|
|
6078
|
+
update[id] = entry;
|
|
6079
|
+
});
|
|
6080
|
+
this._entityOSMMap.set(update);
|
|
4674
6081
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
6082
|
+
/**
|
|
6083
|
+
* Fetches incoming and outgoing relationships for a single entity IRI.
|
|
6084
|
+
* Neighbouring entity core data is opportunistically populated into
|
|
6085
|
+
* `entityInfoMap` as a side-effect.
|
|
6086
|
+
*
|
|
6087
|
+
* The result is stored in `entityInfoMap[uuid].relationshipData` and also
|
|
6088
|
+
* returned directly for callers that need an immediate value.
|
|
6089
|
+
*/
|
|
6090
|
+
async fetchEntityRelationships(iri) {
|
|
6091
|
+
const uuid = iri.replace(/^.*\/([^/]*)$/, "$1");
|
|
6092
|
+
this._entityRelationships.set({
|
|
6093
|
+
...this._entityRelationships.get(),
|
|
6094
|
+
[uuid]: { incoming: [], outgoing: [] }
|
|
6095
|
+
});
|
|
6096
|
+
const [outgoing, incoming] = await Promise.all([
|
|
6097
|
+
this._fetchOutgoingRelationships(iri),
|
|
6098
|
+
this._fetchIncomingRelationships(iri)
|
|
6099
|
+
]);
|
|
6100
|
+
const result = { outgoing, incoming };
|
|
6101
|
+
this._entityRelationships.set({ ...this._entityRelationships.get(), [uuid]: result });
|
|
6102
|
+
return result;
|
|
4679
6103
|
}
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
} catch (err) {
|
|
4693
|
-
throw new Error(
|
|
4694
|
-
`QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
|
|
4695
|
-
);
|
|
6104
|
+
/**
|
|
6105
|
+
* Fetches UUIDs of documents that reference the given entity IRI.
|
|
6106
|
+
* Also triggers a core-data fetch for the entity itself if not yet loaded.
|
|
6107
|
+
*
|
|
6108
|
+
* Returns document UUIDs. Full document data will be populated by the
|
|
6109
|
+
* document service (future `CueProjectDocuments`).
|
|
6110
|
+
*/
|
|
6111
|
+
async fetchEntityDocuments(iri) {
|
|
6112
|
+
const uuid = iri.replace(/^.*\/([^/]*)$/, "$1");
|
|
6113
|
+
this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: [] });
|
|
6114
|
+
if (this._entityDetails.get()[uuid] === void 0) {
|
|
6115
|
+
this.requestEntityData([uuid]);
|
|
4696
6116
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
6117
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6118
|
+
SELECT DISTINCT ?id
|
|
6119
|
+
WHERE {
|
|
6120
|
+
BIND(<${iri}> AS ?iri)
|
|
6121
|
+
?doc a qcy:FileContent ;
|
|
6122
|
+
qcy:about ?iri .
|
|
6123
|
+
BIND(REPLACE(STR(?doc), "^.*/([^/]*)$", "$1") AS ?id)
|
|
6124
|
+
}`;
|
|
6125
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6126
|
+
const ids = data.results.bindings.filter((b) => b["id"] !== void 0).map((b) => b["id"].value);
|
|
6127
|
+
this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: ids });
|
|
6128
|
+
return ids;
|
|
6129
|
+
}
|
|
6130
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
6131
|
+
_computeEntityInfoMap() {
|
|
6132
|
+
const details = this._entityDetails.get();
|
|
6133
|
+
const docs = this._entityDocuments.get();
|
|
6134
|
+
const rels = this._entityRelationships.get();
|
|
6135
|
+
const osmMap = this._entityOSMMap.get();
|
|
6136
|
+
const wktMap = this._osmWKTMap.get();
|
|
6137
|
+
const allIds = /* @__PURE__ */ new Set([
|
|
6138
|
+
...Object.keys(details),
|
|
6139
|
+
...Object.keys(osmMap).filter((k) => {
|
|
6140
|
+
const e = osmMap[k];
|
|
6141
|
+
return e.direct.length > 0 || e.indirect.length > 0;
|
|
6142
|
+
})
|
|
6143
|
+
]);
|
|
6144
|
+
const result = {};
|
|
6145
|
+
for (const id of allIds) {
|
|
6146
|
+
const data = details[id];
|
|
6147
|
+
const osmData = osmMap[id];
|
|
6148
|
+
let directMapGeometries;
|
|
6149
|
+
let indirectMapGeometries;
|
|
6150
|
+
if (osmData) {
|
|
6151
|
+
const directSet = /* @__PURE__ */ new Set();
|
|
6152
|
+
directMapGeometries = osmData.direct.filter(
|
|
6153
|
+
(osm) => wktMap[osm] !== void 0 && !directSet.has(osm) && Boolean(directSet.add(osm))
|
|
6154
|
+
).map((osm) => ({ osmIRI: osm, wkt: wktMap[osm] }));
|
|
6155
|
+
const byRel = /* @__PURE__ */ new Map();
|
|
6156
|
+
for (const { osm, viaRels, entityUUID } of osmData.indirect) {
|
|
6157
|
+
const wkt = wktMap[osm];
|
|
6158
|
+
if (!wkt)
|
|
6159
|
+
continue;
|
|
6160
|
+
for (const rel of viaRels) {
|
|
6161
|
+
const key = `${rel}:${entityUUID}`;
|
|
6162
|
+
const entry = byRel.get(key) ?? { geometries: [], entityUUID };
|
|
6163
|
+
if (!entry.geometries.some((g) => g.osmIRI === osm)) {
|
|
6164
|
+
entry.geometries.push({ osmIRI: osm, wkt });
|
|
6165
|
+
byRel.set(key, entry);
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
if (byRel.size > 0) {
|
|
6170
|
+
indirectMapGeometries = Array.from(byRel.entries()).map(
|
|
6171
|
+
([key, { geometries, entityUUID }]) => ({
|
|
6172
|
+
rel: key.split(":")[0],
|
|
6173
|
+
geometries,
|
|
6174
|
+
entityUUID
|
|
6175
|
+
})
|
|
6176
|
+
);
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
result[id] = {
|
|
6180
|
+
value: data?.value ?? "",
|
|
6181
|
+
categories: data?.categories ?? [],
|
|
6182
|
+
mentionCount: data?.mentionCount,
|
|
6183
|
+
documentRefs: docs[id],
|
|
6184
|
+
relationshipData: rels[id],
|
|
6185
|
+
directMapGeometries,
|
|
6186
|
+
indirectMapGeometries
|
|
6187
|
+
};
|
|
4700
6188
|
}
|
|
4701
|
-
return
|
|
6189
|
+
return result;
|
|
4702
6190
|
}
|
|
4703
|
-
async
|
|
4704
|
-
const
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
6191
|
+
async _fetchOutgoingRelationships(iri) {
|
|
6192
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6193
|
+
SELECT ?rel ?related (SAMPLE(?val) AS ?nodeValue) (GROUP_CONCAT(STR(?cat); SEPARATOR=";") AS ?categories)
|
|
6194
|
+
WHERE {
|
|
6195
|
+
BIND(<${iri}> AS ?s)
|
|
6196
|
+
?s qcy:relatedEntity ?related ;
|
|
6197
|
+
?rel ?related .
|
|
6198
|
+
?related qcy:hasEntityCategory ?cat ;
|
|
6199
|
+
qcy:value ?val
|
|
6200
|
+
FILTER(?rel != qcy:relatedEntity)
|
|
6201
|
+
}
|
|
6202
|
+
GROUP BY ?rel ?related`;
|
|
6203
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6204
|
+
const result = [];
|
|
6205
|
+
const detailsUpdate = { ...this._entityDetails.get() };
|
|
6206
|
+
data.results.bindings.forEach((b) => {
|
|
6207
|
+
if (!b["rel"] || !b["related"])
|
|
6208
|
+
return;
|
|
6209
|
+
const nodeCategories = b["categories"]?.value?.split(";").filter(Boolean) ?? [];
|
|
6210
|
+
result.push({
|
|
6211
|
+
relIRI: b["rel"].value,
|
|
6212
|
+
nodeIRI: b["related"].value,
|
|
6213
|
+
nodeValue: b["nodeValue"]?.value ?? "",
|
|
6214
|
+
nodeCategories
|
|
6215
|
+
});
|
|
6216
|
+
const nodeUUID = b["related"].value.replace(/^.*\/([^/]*)$/, "$1");
|
|
6217
|
+
detailsUpdate[nodeUUID] = {
|
|
6218
|
+
value: b["nodeValue"]?.value ?? "",
|
|
6219
|
+
categories: nodeCategories
|
|
6220
|
+
};
|
|
4712
6221
|
});
|
|
4713
|
-
|
|
6222
|
+
this._entityDetails.set(detailsUpdate);
|
|
6223
|
+
return result;
|
|
4714
6224
|
}
|
|
4715
|
-
async
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
6225
|
+
async _fetchIncomingRelationships(iri) {
|
|
6226
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6227
|
+
SELECT ?rel ?relating (SAMPLE(?val) AS ?nodeValue) (GROUP_CONCAT(STR(?cat); SEPARATOR=";") AS ?categories)
|
|
6228
|
+
WHERE {
|
|
6229
|
+
BIND(<${iri}> AS ?s)
|
|
6230
|
+
?relating qcy:relatedEntity ?s ;
|
|
6231
|
+
?rel ?s .
|
|
6232
|
+
?relating qcy:hasEntityCategory ?cat ;
|
|
6233
|
+
qcy:value ?val
|
|
6234
|
+
FILTER(?rel != qcy:relatedEntity)
|
|
6235
|
+
}
|
|
6236
|
+
GROUP BY ?rel ?relating`;
|
|
6237
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6238
|
+
const result = [];
|
|
6239
|
+
const detailsUpdate = { ...this._entityDetails.get() };
|
|
6240
|
+
data.results.bindings.forEach((b) => {
|
|
6241
|
+
if (!b["rel"] || !b["relating"])
|
|
6242
|
+
return;
|
|
6243
|
+
const nodeCategories = b["categories"]?.value?.split(";").filter(Boolean) ?? [];
|
|
6244
|
+
result.push({
|
|
6245
|
+
relIRI: b["rel"].value,
|
|
6246
|
+
nodeIRI: b["relating"].value,
|
|
6247
|
+
nodeValue: b["nodeValue"]?.value ?? "",
|
|
6248
|
+
nodeCategories
|
|
4724
6249
|
});
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
6250
|
+
const nodeUUID = b["relating"].value.replace(/^.*\/([^/]*)$/, "$1");
|
|
6251
|
+
detailsUpdate[nodeUUID] = {
|
|
6252
|
+
value: b["nodeValue"]?.value ?? "",
|
|
6253
|
+
categories: nodeCategories
|
|
6254
|
+
};
|
|
6255
|
+
});
|
|
6256
|
+
this._entityDetails.set(detailsUpdate);
|
|
6257
|
+
return result;
|
|
6258
|
+
}
|
|
6259
|
+
async _fetchEntityGraph() {
|
|
6260
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6261
|
+
SELECT ?e1Cat ?e2Cat (COUNT(?e1) AS ?e1Count) (COUNT(?e2) AS ?e2Count)
|
|
6262
|
+
WHERE {
|
|
6263
|
+
?e1 a qcy:CanonicalEntity ;
|
|
6264
|
+
qcy:hasEntityCategory ?e1Cat ;
|
|
6265
|
+
qcy:relatedEntity ?e2 .
|
|
6266
|
+
?e2 qcy:hasEntityCategory ?e2Cat
|
|
6267
|
+
}
|
|
6268
|
+
GROUP BY ?e1Cat ?e2Cat`;
|
|
6269
|
+
await staleWhileRevalidate(
|
|
6270
|
+
q,
|
|
6271
|
+
async () => {
|
|
6272
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6273
|
+
const entities = [];
|
|
6274
|
+
const relations = [];
|
|
6275
|
+
data.results.bindings.forEach((b) => {
|
|
6276
|
+
if (!b["e1Cat"] || !b["e2Cat"])
|
|
6277
|
+
return;
|
|
6278
|
+
const e1Cat = b["e1Cat"].value;
|
|
6279
|
+
const e2Cat = b["e2Cat"].value;
|
|
6280
|
+
const e1Size = b["e1Count"] ? parseInt(b["e1Count"].value, 10) || 20 : 20;
|
|
6281
|
+
const e2Size = b["e2Count"] ? parseInt(b["e2Count"].value, 10) || 20 : 20;
|
|
6282
|
+
if (!entities.some((n) => n.iri === e1Cat))
|
|
6283
|
+
entities.push({ iri: e1Cat, size: e1Size });
|
|
6284
|
+
if (!entities.some((n) => n.iri === e2Cat))
|
|
6285
|
+
entities.push({ iri: e2Cat, size: e2Size });
|
|
6286
|
+
relations.push({ sourceID: e1Cat, targetID: e2Cat });
|
|
6287
|
+
});
|
|
6288
|
+
return { entities, relations };
|
|
6289
|
+
},
|
|
6290
|
+
(graph) => this._entityGraph.set(graph),
|
|
6291
|
+
this._queryCache
|
|
6292
|
+
);
|
|
6293
|
+
}
|
|
6294
|
+
/** Detects new OSM IRIs in the OSM map that don't yet have WKT and fetches them. */
|
|
6295
|
+
_checkPendingOSMFetches() {
|
|
6296
|
+
const osmMap = this._entityOSMMap.get();
|
|
6297
|
+
const wktMap = this._osmWKTMap.get();
|
|
6298
|
+
const pending = [];
|
|
6299
|
+
for (const entry of Object.values(osmMap)) {
|
|
6300
|
+
for (const osm of entry.direct) {
|
|
6301
|
+
if (!wktMap[osm] && !this._fetchingOSMIds.has(osm))
|
|
6302
|
+
pending.push(osm);
|
|
4730
6303
|
}
|
|
4731
|
-
|
|
6304
|
+
for (const { osm } of entry.indirect) {
|
|
6305
|
+
if (!wktMap[osm] && !this._fetchingOSMIds.has(osm))
|
|
6306
|
+
pending.push(osm);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
if (pending.length === 0)
|
|
6310
|
+
return;
|
|
6311
|
+
for (const osm of pending)
|
|
6312
|
+
this._fetchingOSMIds.add(osm);
|
|
6313
|
+
this._fetchOSMLocations(pending).catch(
|
|
6314
|
+
(err) => console.error("[CueProjectEntities] OSM WKT fetch failed:", err)
|
|
6315
|
+
);
|
|
6316
|
+
}
|
|
6317
|
+
async _fetchOSMLocations(osmIRIs) {
|
|
6318
|
+
const filtered = EXCLUDE_OSM_RELATION ? osmIRIs.filter((iri) => !iri.includes("/relation/")) : osmIRIs;
|
|
6319
|
+
if (filtered.length === 0)
|
|
6320
|
+
return;
|
|
6321
|
+
const values = filtered.map((iri) => `<${iri}>`).join(" ");
|
|
6322
|
+
const q = `PREFIX geo: <${prefixCC["geo"]}>
|
|
6323
|
+
SELECT * WHERE {
|
|
6324
|
+
VALUES ?s { ${values} }
|
|
6325
|
+
SERVICE <${OSM_ENDPOINT}> {
|
|
6326
|
+
?s geo:hasGeometry/geo:asWKT ?wkt
|
|
6327
|
+
}
|
|
6328
|
+
}`;
|
|
6329
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6330
|
+
const update = { ...this._osmWKTMap.get() };
|
|
6331
|
+
data.results.bindings.forEach((b) => {
|
|
6332
|
+
if (!b["s"] || !b["wkt"])
|
|
6333
|
+
return;
|
|
6334
|
+
update[b["s"].value] = b["wkt"].value;
|
|
6335
|
+
this._fetchingOSMIds.delete(b["s"].value);
|
|
4732
6336
|
});
|
|
6337
|
+
this._osmWKTMap.set(update);
|
|
4733
6338
|
}
|
|
6339
|
+
};
|
|
6340
|
+
|
|
6341
|
+
// libs/js/cue-sdk/src/lib/documents.ts
|
|
6342
|
+
var CueProjectDocuments = class {
|
|
6343
|
+
constructor(_api, _projectId, language, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
6344
|
+
this._api = _api;
|
|
6345
|
+
this._projectId = _projectId;
|
|
6346
|
+
this._queryCache = _queryCache;
|
|
6347
|
+
this._graphType = _graphType;
|
|
6348
|
+
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
6349
|
+
this._language = language;
|
|
6350
|
+
this.documentInfoMap = this._documentInfoMap.asReadonly();
|
|
6351
|
+
this.projectDocumentsData = this._projectDocumentsData.asReadonly();
|
|
6352
|
+
}
|
|
6353
|
+
/** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
|
|
6354
|
+
baseURL;
|
|
6355
|
+
_language;
|
|
6356
|
+
_documentInfoMap = new CueSignal({});
|
|
6357
|
+
_projectDocumentsData = new CueSignal({
|
|
6358
|
+
duplicateCount: 0,
|
|
6359
|
+
documentsBySuffix: {},
|
|
6360
|
+
documentsByContentCategory: {}
|
|
6361
|
+
});
|
|
6362
|
+
/** Lazily populated per-document detail map. */
|
|
6363
|
+
documentInfoMap;
|
|
6364
|
+
/** Project-level document overview (grouped counts + sizes). */
|
|
6365
|
+
projectDocumentsData;
|
|
6366
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
4734
6367
|
/**
|
|
4735
|
-
*
|
|
4736
|
-
*
|
|
4737
|
-
* the /data endpoint correctly registers named graphs in the index.
|
|
6368
|
+
* Resets all document state. Call when the active project changes.
|
|
6369
|
+
* Follow with `fetchOverview()` once the triplestore is ready.
|
|
4738
6370
|
*/
|
|
4739
|
-
|
|
4740
|
-
|
|
6371
|
+
reset() {
|
|
6372
|
+
this._documentInfoMap.set({});
|
|
6373
|
+
this._projectDocumentsData.set({
|
|
6374
|
+
duplicateCount: 0,
|
|
6375
|
+
documentsBySuffix: {},
|
|
6376
|
+
documentsByContentCategory: {}
|
|
6377
|
+
});
|
|
4741
6378
|
}
|
|
4742
6379
|
/**
|
|
4743
|
-
*
|
|
6380
|
+
* Updates the active language and clears the document info map so that
|
|
6381
|
+
* language-sensitive fields (subject, summary) are re-fetched on the next
|
|
6382
|
+
* `requestDocumentData()` call.
|
|
4744
6383
|
*/
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
const body = new Uint8Array((0, import_zlib.gzipSync)(Buffer.from(nquads, "utf-8")));
|
|
4751
|
-
await _QLever._retryOnLocked(async () => {
|
|
4752
|
-
const res = await fetch(baseUrl, {
|
|
4753
|
-
method: "POST",
|
|
4754
|
-
headers: {
|
|
4755
|
-
...this.baseHeaders,
|
|
4756
|
-
"Content-Type": "application/n-quads",
|
|
4757
|
-
"Content-Encoding": "gzip"
|
|
4758
|
-
},
|
|
4759
|
-
body
|
|
4760
|
-
});
|
|
4761
|
-
if (!res.ok) {
|
|
4762
|
-
const text = await res.text();
|
|
4763
|
-
if (res.status === 423)
|
|
4764
|
-
throw new QLeverLockedError(text);
|
|
4765
|
-
throw new Error(`QLever data POST failed (HTTP ${res.status}): ${text}`);
|
|
4766
|
-
}
|
|
4767
|
-
});
|
|
6384
|
+
setLanguage(lang) {
|
|
6385
|
+
if (this._language === lang)
|
|
6386
|
+
return;
|
|
6387
|
+
this._language = lang;
|
|
6388
|
+
this._documentInfoMap.set({});
|
|
4768
6389
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
6390
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
6391
|
+
/**
|
|
6392
|
+
* Fetches the three-part project overview (by suffix, by content category,
|
|
6393
|
+
* duplicate count) in parallel and writes them as a single atomic update to
|
|
6394
|
+
* `projectDocumentsData`. Safe to call again to refresh.
|
|
6395
|
+
*/
|
|
6396
|
+
async fetchOverview() {
|
|
6397
|
+
this._projectDocumentsData.set({
|
|
6398
|
+
duplicateCount: 0,
|
|
6399
|
+
documentsBySuffix: {},
|
|
6400
|
+
documentsByContentCategory: {}
|
|
4774
6401
|
});
|
|
6402
|
+
const qSuffix = this._buildDocumentsBySuffixQuery();
|
|
6403
|
+
const qCategory = this._buildDocumentsByContentCategoryQuery();
|
|
6404
|
+
const qDuplicates = this._buildDuplicateCountQuery();
|
|
6405
|
+
await staleWhileRevalidate(
|
|
6406
|
+
qSuffix + qCategory + qDuplicates,
|
|
6407
|
+
async () => {
|
|
6408
|
+
const [bySuffix, byCategory, duplicateCount] = await Promise.all([
|
|
6409
|
+
this._runDocumentsBySuffixQuery(qSuffix),
|
|
6410
|
+
this._runDocumentsByContentCategoryQuery(qCategory),
|
|
6411
|
+
this._runDuplicateCountQuery(qDuplicates)
|
|
6412
|
+
]);
|
|
6413
|
+
return { duplicateCount, documentsBySuffix: bySuffix, documentsByContentCategory: byCategory };
|
|
6414
|
+
},
|
|
6415
|
+
(overview) => this._projectDocumentsData.set(overview),
|
|
6416
|
+
this._queryCache
|
|
6417
|
+
);
|
|
4775
6418
|
}
|
|
4776
|
-
|
|
6419
|
+
/**
|
|
6420
|
+
* Lazily batch-fetches core metadata for the given document UUIDs.
|
|
6421
|
+
* Already-cached UUIDs are skipped. Data is merged into `documentInfoMap`
|
|
6422
|
+
* once the SPARQL response arrives.
|
|
6423
|
+
*/
|
|
6424
|
+
requestDocumentData(uuids) {
|
|
6425
|
+
const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
|
|
6426
|
+
if (newUUIDs.length === 0)
|
|
6427
|
+
return;
|
|
6428
|
+
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
6429
|
+
const lang = this._language;
|
|
6430
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6431
|
+
PREFIX r: <${this.baseURL}>
|
|
6432
|
+
SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
|
|
6433
|
+
(SAMPLE(?fp) AS ?path)
|
|
6434
|
+
(GROUP_CONCAT(DISTINCT ?tag; SEPARATOR=";") AS ?tags)
|
|
6435
|
+
(GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories)
|
|
6436
|
+
WHERE {
|
|
6437
|
+
VALUES ?contentIRI { ${values} }
|
|
6438
|
+
?contentIRI qcy:sizeBytes ?size ;
|
|
6439
|
+
qcy:hasFileLocation ?loc .
|
|
6440
|
+
?loc qcy:filePath ?fp ;
|
|
6441
|
+
qcy:suffix ?suffix .
|
|
6442
|
+
OPTIONAL { ?contentIRI qcy:hasContentCategory ?cat }
|
|
6443
|
+
OPTIONAL { ?contentIRI qcy:tag ?tag }
|
|
6444
|
+
OPTIONAL { ?loc qcy:remoteProviderId ?pid }
|
|
4777
6445
|
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
6446
|
+
OPTIONAL { ?contentIRI qcy:subject ?lang_subj FILTER(LANG(?lang_subj) = "${lang}") }
|
|
6447
|
+
OPTIONAL { ?contentIRI qcy:subject ?no_lang_subj }
|
|
6448
|
+
BIND(COALESCE(?lang_subj, ?no_lang_subj) AS ?subject)
|
|
6449
|
+
|
|
6450
|
+
OPTIONAL { ?contentIRI qcy:textSummary ?lang_summary FILTER(LANG(?lang_summary) = "${lang}") }
|
|
6451
|
+
OPTIONAL { ?contentIRI qcy:textSummary ?no_lang_summary }
|
|
6452
|
+
BIND(COALESCE(?lang_summary, ?no_lang_summary) AS ?summary)
|
|
6453
|
+
|
|
6454
|
+
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
|
|
6455
|
+
}
|
|
6456
|
+
GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
|
|
6457
|
+
this._api.sparql(q, this._projectId).then((data) => {
|
|
6458
|
+
const result = data;
|
|
6459
|
+
const updates = { ...this._documentInfoMap.get() };
|
|
6460
|
+
result.results.bindings.forEach((b) => {
|
|
6461
|
+
if (!b["id"] || !b["contentIRI"])
|
|
6462
|
+
return;
|
|
6463
|
+
const id = b["id"].value;
|
|
6464
|
+
updates[id] = {
|
|
6465
|
+
id,
|
|
6466
|
+
contentIRI: b["contentIRI"].value,
|
|
6467
|
+
path: b["path"]?.value ?? "",
|
|
6468
|
+
suffix: b["suffix"]?.value ?? "",
|
|
6469
|
+
size: b["size"] ? parseInt(b["size"].value, 10) : 0,
|
|
6470
|
+
tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
|
|
6471
|
+
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
|
|
6472
|
+
subject: b["subject"]?.value,
|
|
6473
|
+
summary: b["summary"]?.value,
|
|
6474
|
+
providerId: b["pid"]?.value
|
|
6475
|
+
};
|
|
6476
|
+
});
|
|
6477
|
+
this._documentInfoMap.set(updates);
|
|
6478
|
+
}).catch(
|
|
6479
|
+
(err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
|
|
6480
|
+
);
|
|
6481
|
+
}
|
|
6482
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
6483
|
+
async _fetchDocumentsBySuffix() {
|
|
6484
|
+
return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
|
|
6485
|
+
}
|
|
6486
|
+
_buildDocumentsBySuffixQuery() {
|
|
6487
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6488
|
+
SELECT ?ext (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
6489
|
+
WHERE {
|
|
6490
|
+
{
|
|
6491
|
+
SELECT DISTINCT ?fc ?ext ?size
|
|
6492
|
+
WHERE {
|
|
6493
|
+
?fc a qcy:FileContent ;
|
|
6494
|
+
qcy:sizeBytes ?size ;
|
|
6495
|
+
qcy:hasFileLocation/qcy:suffix ?ext .
|
|
6496
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
4791
6497
|
}
|
|
4792
6498
|
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
6499
|
+
}
|
|
6500
|
+
GROUP BY ?ext
|
|
6501
|
+
ORDER BY DESC(?docCount)`;
|
|
6502
|
+
}
|
|
6503
|
+
async _runDocumentsBySuffixQuery(q) {
|
|
6504
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6505
|
+
const result = {};
|
|
6506
|
+
data.results.bindings.forEach((b) => {
|
|
6507
|
+
if (!b["ext"])
|
|
6508
|
+
return;
|
|
6509
|
+
result[b["ext"].value] = {
|
|
6510
|
+
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
6511
|
+
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
6512
|
+
};
|
|
6513
|
+
});
|
|
6514
|
+
return result;
|
|
4796
6515
|
}
|
|
4797
|
-
|
|
4798
|
-
return this.
|
|
6516
|
+
async _fetchDocumentsByContentCategory() {
|
|
6517
|
+
return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
|
|
4799
6518
|
}
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
accept
|
|
4812
|
-
);
|
|
6519
|
+
_buildDocumentsByContentCategoryQuery() {
|
|
6520
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6521
|
+
SELECT ?cat (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
6522
|
+
WHERE {
|
|
6523
|
+
{
|
|
6524
|
+
SELECT DISTINCT ?fc ?cat ?size
|
|
6525
|
+
WHERE {
|
|
6526
|
+
?fc a qcy:FileContent ;
|
|
6527
|
+
qcy:hasContentCategory ?cat ;
|
|
6528
|
+
qcy:sizeBytes ?size .
|
|
6529
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
4813
6530
|
}
|
|
4814
|
-
return this._db.subset(queryString, accept);
|
|
4815
6531
|
}
|
|
4816
|
-
|
|
4817
|
-
|
|
6532
|
+
}
|
|
6533
|
+
GROUP BY ?cat
|
|
6534
|
+
ORDER BY DESC(?docCount)`;
|
|
6535
|
+
}
|
|
6536
|
+
async _runDocumentsByContentCategoryQuery(q) {
|
|
6537
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6538
|
+
const result = {};
|
|
6539
|
+
data.results.bindings.forEach((b) => {
|
|
6540
|
+
if (!b["cat"])
|
|
6541
|
+
return;
|
|
6542
|
+
result[b["cat"].value] = {
|
|
6543
|
+
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
6544
|
+
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
6545
|
+
};
|
|
6546
|
+
});
|
|
6547
|
+
return result;
|
|
4818
6548
|
}
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
6549
|
+
async _fetchDuplicateCount() {
|
|
6550
|
+
return this._runDuplicateCountQuery(this._buildDuplicateCountQuery());
|
|
6551
|
+
}
|
|
6552
|
+
_buildDuplicateCountQuery() {
|
|
6553
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
6554
|
+
SELECT (COUNT(*) AS ?count)
|
|
6555
|
+
WHERE {
|
|
6556
|
+
SELECT ?fc
|
|
6557
|
+
WHERE {
|
|
6558
|
+
?fc a qcy:FileContent ;
|
|
6559
|
+
qcy:hasFileLocation ?fl .
|
|
6560
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
6561
|
+
}
|
|
6562
|
+
GROUP BY ?fc
|
|
6563
|
+
HAVING (COUNT(?fl) > 1)
|
|
6564
|
+
}`;
|
|
6565
|
+
}
|
|
6566
|
+
async _runDuplicateCountQuery(q) {
|
|
6567
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
6568
|
+
const first = data.results.bindings[0];
|
|
6569
|
+
return first?.["count"] ? parseInt(first["count"].value, 10) : 0;
|
|
6570
|
+
}
|
|
6571
|
+
};
|
|
6572
|
+
|
|
6573
|
+
// libs/js/cue-sdk/src/lib/project-view.ts
|
|
6574
|
+
var CueProjectView = class {
|
|
6575
|
+
constructor(_api, _projectId, { language, queryCache, rdfBase = RESOURCE_BASE, graphType }) {
|
|
6576
|
+
this._api = _api;
|
|
6577
|
+
this._projectId = _projectId;
|
|
6578
|
+
this.schema = new CueProjectSchema(_api, _projectId, language, queryCache, graphType);
|
|
6579
|
+
this.entities = new CueProjectEntities(_api, _projectId, rdfBase, queryCache, graphType);
|
|
6580
|
+
this.documents = new CueProjectDocuments(_api, _projectId, language, rdfBase, queryCache, graphType);
|
|
6581
|
+
this.availableContentCategories = this.schema.availableContentCategories;
|
|
6582
|
+
this.availableEntityCategories = this.schema.availableEntityCategories;
|
|
6583
|
+
this.availableEntityRelationships = this.schema.availableEntityRelationships;
|
|
6584
|
+
this.entityInfoMap = this.entities.entityInfoMap;
|
|
6585
|
+
this.entityGraph = this.entities.entityGraph;
|
|
6586
|
+
this.documentInfoMap = this.documents.documentInfoMap;
|
|
6587
|
+
this.projectDocumentsData = this.documents.projectDocumentsData;
|
|
6588
|
+
this.searchResults = this._searchResults.asReadonly();
|
|
6589
|
+
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed:", err));
|
|
6590
|
+
}
|
|
6591
|
+
/** Direct access to the schema data class (available categories / relationships). */
|
|
6592
|
+
schema;
|
|
6593
|
+
/** Direct access to the entity data class. */
|
|
6594
|
+
entities;
|
|
6595
|
+
/** Direct access to the document data class. */
|
|
6596
|
+
documents;
|
|
6597
|
+
// ── Proxied signals ────────────────────────────────────────────────────────
|
|
6598
|
+
/** Available content category definitions for this project. Auto-fetched on init. */
|
|
6599
|
+
availableContentCategories;
|
|
6600
|
+
/** Available entity category definitions for this project. Auto-fetched on init. */
|
|
6601
|
+
availableEntityCategories;
|
|
6602
|
+
/** Available entity relationship types. Auto-fetched on init. */
|
|
6603
|
+
availableEntityRelationships;
|
|
6604
|
+
/** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
|
|
6605
|
+
entityInfoMap;
|
|
6606
|
+
/** Project-level entity co-occurrence graph. Fetched once on init. */
|
|
6607
|
+
entityGraph;
|
|
6608
|
+
/** Per-document info map. Populated lazily via `requestDocumentData()`. */
|
|
6609
|
+
documentInfoMap;
|
|
6610
|
+
/** Project document overview (counts by suffix and category). Fetched on init. */
|
|
6611
|
+
projectDocumentsData;
|
|
6612
|
+
// ── Search state ───────────────────────────────────────────────────────────
|
|
6613
|
+
_searchResults = new CueSignal(void 0);
|
|
6614
|
+
/** The result of the most recent `search()` call. `undefined` before first search. */
|
|
6615
|
+
searchResults;
|
|
6616
|
+
_destroyed = false;
|
|
6617
|
+
// ── Entity methods ─────────────────────────────────────────────────────────
|
|
6618
|
+
/**
|
|
6619
|
+
* Lazily batch-fetch core data (label + categories) for the given entity UUIDs.
|
|
6620
|
+
* Already-fetched UUIDs are skipped. Populates `entityInfoMap`.
|
|
6621
|
+
*/
|
|
6622
|
+
requestEntityData(uuids, includeMentionCount = false) {
|
|
6623
|
+
if (this._destroyed)
|
|
6624
|
+
return;
|
|
6625
|
+
this.entities.requestEntityData(uuids, includeMentionCount);
|
|
4822
6626
|
}
|
|
4823
6627
|
/**
|
|
4824
|
-
*
|
|
4825
|
-
*
|
|
4826
|
-
* For Fuseki: falls back to SPARQL INSERT DATA.
|
|
6628
|
+
* Lazily fetch OSM location data for the given entity UUIDs.
|
|
6629
|
+
* Already-fetched UUIDs are skipped. Populates `entityInfoMap` geometry fields.
|
|
4827
6630
|
*/
|
|
4828
|
-
|
|
4829
|
-
if (this.
|
|
4830
|
-
return
|
|
4831
|
-
|
|
4832
|
-
return Promise.reject(new Error("insertData not supported for Fuseki \u2014 use update() with SPARQL INSERT DATA"));
|
|
6631
|
+
async requestEntityLocations(uuids) {
|
|
6632
|
+
if (this._destroyed)
|
|
6633
|
+
return;
|
|
6634
|
+
return this.entities.requestEntityLocations(uuids);
|
|
4833
6635
|
}
|
|
4834
6636
|
/**
|
|
4835
|
-
*
|
|
4836
|
-
*
|
|
4837
|
-
* For Fuseki: falls back to SPARQL DELETE DATA.
|
|
6637
|
+
* Fetch incoming and outgoing relationships for a single entity IRI.
|
|
6638
|
+
* Result is stored in `entityInfoMap[uuid].relationshipData`.
|
|
4838
6639
|
*/
|
|
4839
|
-
|
|
4840
|
-
if (this.
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
return Promise.reject(new Error("deleteData not supported for Fuseki \u2014 use update() with SPARQL DELETE DATA"));
|
|
6640
|
+
async fetchEntityRelationships(iri) {
|
|
6641
|
+
if (this._destroyed)
|
|
6642
|
+
throw new Error("CueProjectView is destroyed");
|
|
6643
|
+
return this.entities.fetchEntityRelationships(iri);
|
|
4844
6644
|
}
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
6645
|
+
/**
|
|
6646
|
+
* Fetch UUIDs of documents that reference the given entity IRI.
|
|
6647
|
+
* Result is stored in `entityInfoMap[uuid].documentRefs`.
|
|
6648
|
+
*/
|
|
6649
|
+
async fetchEntityDocuments(iri) {
|
|
6650
|
+
if (this._destroyed)
|
|
6651
|
+
throw new Error("CueProjectView is destroyed");
|
|
6652
|
+
return this.entities.fetchEntityDocuments(iri);
|
|
6653
|
+
}
|
|
6654
|
+
/** Constructs the full RDF IRI for an entity UUID. */
|
|
6655
|
+
entityIri(uuid) {
|
|
6656
|
+
return this.entities.entityIri(uuid);
|
|
4852
6657
|
}
|
|
6658
|
+
// ── Document methods ───────────────────────────────────────────────────────
|
|
4853
6659
|
/**
|
|
4854
|
-
*
|
|
6660
|
+
* Lazily batch-fetch document info for the given UUIDs.
|
|
6661
|
+
* Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
|
|
4855
6662
|
*/
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
6663
|
+
requestDocumentData(uuids) {
|
|
6664
|
+
if (this._destroyed)
|
|
6665
|
+
return;
|
|
6666
|
+
this.documents.requestDocumentData(uuids);
|
|
6667
|
+
}
|
|
6668
|
+
// ── Search ─────────────────────────────────────────────────────────────────
|
|
6669
|
+
/**
|
|
6670
|
+
* Run a natural-language search against the project.
|
|
6671
|
+
* The result is stored in `searchResults` and replaces any previous result.
|
|
6672
|
+
*/
|
|
6673
|
+
async search(term, options) {
|
|
6674
|
+
if (this._destroyed)
|
|
6675
|
+
return;
|
|
6676
|
+
const result = await this._api.search({
|
|
6677
|
+
term,
|
|
6678
|
+
projectId: this._projectId,
|
|
6679
|
+
categories: options?.categories
|
|
6680
|
+
});
|
|
6681
|
+
if (!this._destroyed) {
|
|
6682
|
+
this._searchResults.set(result);
|
|
4876
6683
|
}
|
|
4877
|
-
throw lastError;
|
|
4878
6684
|
}
|
|
6685
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
4879
6686
|
/**
|
|
4880
|
-
*
|
|
4881
|
-
*
|
|
6687
|
+
* Switch the active language for schema labels and document text fields.
|
|
6688
|
+
* Schema responses are cached per language (instant if previously loaded).
|
|
6689
|
+
* The document info map is cleared and lazily re-populated on next access.
|
|
4882
6690
|
*/
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
await (0, import_storage3.uploadBytes)(fileRef, data, { customMetadata: metadata });
|
|
4889
|
-
return true;
|
|
6691
|
+
setLanguage(lang) {
|
|
6692
|
+
if (this._destroyed)
|
|
6693
|
+
return;
|
|
6694
|
+
this.schema.setLanguage(lang);
|
|
6695
|
+
this.documents.setLanguage(lang);
|
|
4890
6696
|
}
|
|
4891
6697
|
/**
|
|
4892
|
-
*
|
|
6698
|
+
* Reset all entity and document state and re-fetch the project overview.
|
|
6699
|
+
* Prefer creating a fresh `CueProjectView` when switching projects.
|
|
6700
|
+
* Use `reset()` only when the same project's data needs to be invalidated.
|
|
4893
6701
|
*/
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
6702
|
+
reset() {
|
|
6703
|
+
if (this._destroyed)
|
|
6704
|
+
return;
|
|
6705
|
+
this.entities.reset();
|
|
6706
|
+
this.documents.reset();
|
|
6707
|
+
this._searchResults.set(void 0);
|
|
6708
|
+
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed after reset:", err));
|
|
4898
6709
|
}
|
|
4899
6710
|
/**
|
|
4900
|
-
*
|
|
6711
|
+
* Tear down this view instance. Clears all reactive state and blocks further
|
|
6712
|
+
* updates. Call from the Angular adapter's `ngOnDestroy` or equivalent.
|
|
4901
6713
|
*/
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
4906
|
-
try {
|
|
4907
|
-
const [url, metadata] = await Promise.all([
|
|
4908
|
-
(0, import_storage3.getDownloadURL)(fileRef),
|
|
4909
|
-
(0, import_storage3.getMetadata)(fileRef)
|
|
4910
|
-
]);
|
|
4911
|
-
const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
|
|
4912
|
-
const res = await fetch(cacheBustedUrl, { signal: controller.signal });
|
|
4913
|
-
if (!res.ok)
|
|
4914
|
-
throw new Error(`HTTP ${res.status}`);
|
|
4915
|
-
return res.text();
|
|
4916
|
-
} catch (err) {
|
|
4917
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
4918
|
-
throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
|
|
4919
|
-
} finally {
|
|
4920
|
-
clearTimeout(timeout);
|
|
4921
|
-
}
|
|
6714
|
+
destroy() {
|
|
6715
|
+
this._destroyed = true;
|
|
6716
|
+
this._searchResults.set(void 0);
|
|
4922
6717
|
}
|
|
4923
6718
|
};
|
|
4924
6719
|
|
|
4925
|
-
// libs/js/
|
|
4926
|
-
|
|
4927
|
-
var import_path3 = require("path");
|
|
4928
|
-
var import_url = require("url");
|
|
4929
|
-
var import_os = require("os");
|
|
6720
|
+
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
6721
|
+
init_src();
|
|
4930
6722
|
|
|
4931
6723
|
// libs/js/models/src/lib/file-extensions.ts
|
|
4932
6724
|
var fileExtensionsInfo = {
|
|
@@ -5828,9 +7620,11 @@ var import_uuid5 = require("uuid");
|
|
|
5828
7620
|
|
|
5829
7621
|
// libs/js/rdf-document-writers/src/lib/alternative-representation.ts
|
|
5830
7622
|
var import_n33 = require("n3");
|
|
7623
|
+
init_src();
|
|
5831
7624
|
|
|
5832
7625
|
// libs/js/rdf-document-writers/src/lib/file-location.ts
|
|
5833
7626
|
var import_n32 = require("n3");
|
|
7627
|
+
init_src();
|
|
5834
7628
|
var { namedNode, literal } = import_n32.DataFactory;
|
|
5835
7629
|
var a = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5836
7630
|
var prefixes = {
|
|
@@ -5853,6 +7647,7 @@ var { namedNode: namedNode3, literal: literal2 } = import_n34.DataFactory;
|
|
|
5853
7647
|
var a2 = namedNode3("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5854
7648
|
|
|
5855
7649
|
// libs/js/rdf-document-writers/src/lib/document-file.ts
|
|
7650
|
+
init_src();
|
|
5856
7651
|
var import_n35 = require("n3");
|
|
5857
7652
|
|
|
5858
7653
|
// libs/js/rdf-document-writers/src/lib/file-suffix.ts
|
|
@@ -5871,6 +7666,7 @@ var a3 = namedNode4("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
|
5871
7666
|
// libs/js/rdf-document-writers/src/lib/process-logs.ts
|
|
5872
7667
|
var import_n36 = require("n3");
|
|
5873
7668
|
var import_uuid6 = require("uuid");
|
|
7669
|
+
init_src();
|
|
5874
7670
|
var { namedNode: namedNode5, literal: literal4 } = import_n36.DataFactory;
|
|
5875
7671
|
var a4 = namedNode5("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5876
7672
|
var prefixes3 = {
|
|
@@ -5911,29 +7707,59 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
|
|
|
5911
7707
|
}
|
|
5912
7708
|
|
|
5913
7709
|
// libs/js/cue-sdk/src/lib/sync.ts
|
|
7710
|
+
async function _readNodeFile(fullPath) {
|
|
7711
|
+
if (typeof window !== "undefined") {
|
|
7712
|
+
throw new Error(
|
|
7713
|
+
`Cannot read file from path "${fullPath}" in a browser environment. Provide file.data (Uint8Array) instead.`
|
|
7714
|
+
);
|
|
7715
|
+
}
|
|
7716
|
+
const { readFile } = await import("fs/promises");
|
|
7717
|
+
return readFile(fullPath);
|
|
7718
|
+
}
|
|
5914
7719
|
var _scanFn = null;
|
|
5915
7720
|
var _wasmInitPromise = null;
|
|
7721
|
+
var _browserWasmBaseUrl = null;
|
|
5916
7722
|
async function _initWasm() {
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
7723
|
+
if (typeof window !== "undefined") {
|
|
7724
|
+
if (!_browserWasmBaseUrl) {
|
|
7725
|
+
throw new Error(
|
|
7726
|
+
"WASM scanner is not configured for browser use. Call configureScanWasm(baseUrl) during app initialisation."
|
|
7727
|
+
);
|
|
7728
|
+
}
|
|
7729
|
+
const wasmResponse = await fetch(`${_browserWasmBaseUrl}/dir_scanner_wasm_bg.wasm`);
|
|
7730
|
+
if (!wasmResponse.ok) {
|
|
7731
|
+
throw new Error(`Failed to fetch WASM binary: ${wasmResponse.status} ${wasmResponse.statusText}`);
|
|
7732
|
+
}
|
|
7733
|
+
const wasmBinary = new Uint8Array(await wasmResponse.arrayBuffer());
|
|
7734
|
+
const glueUrl = `${_browserWasmBaseUrl}/dir_scanner_wasm.mjs`;
|
|
7735
|
+
const mod = await import(
|
|
7736
|
+
/* @vite-ignore */
|
|
7737
|
+
glueUrl
|
|
7738
|
+
);
|
|
7739
|
+
await mod.default({ module_or_path: wasmBinary });
|
|
7740
|
+
_scanFn = mod.scan;
|
|
7741
|
+
} else {
|
|
7742
|
+
const { readFile } = await import("fs/promises");
|
|
7743
|
+
const { join: join4 } = await import("path");
|
|
7744
|
+
const { pathToFileURL } = await import("url");
|
|
7745
|
+
const wasmDir = join4(__dirname, "assets", "wasm");
|
|
7746
|
+
const wasmBinary = await readFile(join4(wasmDir, "dir_scanner_wasm_bg.wasm"));
|
|
7747
|
+
const glueUrl = pathToFileURL(join4(wasmDir, "dir_scanner_wasm.mjs")).href;
|
|
7748
|
+
const mod = await import(
|
|
7749
|
+
/* @vite-ignore */
|
|
7750
|
+
glueUrl
|
|
7751
|
+
);
|
|
7752
|
+
await mod.default({ module_or_path: wasmBinary });
|
|
7753
|
+
_scanFn = mod.scan;
|
|
7754
|
+
}
|
|
5926
7755
|
}
|
|
5927
7756
|
var DEFAULT_GRAPH_TYPE = "fuseki";
|
|
5928
|
-
var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
|
|
5929
|
-
var ENDPOINT_QLEVER_QUERY = "/sparql/query";
|
|
5930
|
-
var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
|
|
5931
|
-
var ENDPOINT_QLEVER_UPDATE = "/sparql/update";
|
|
5932
|
-
var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
|
|
5933
7757
|
var FSS_BATCH_CHUNK_SIZE = 1e3;
|
|
5934
7758
|
var PENDING_LS_PREFIX = "cue:pending:";
|
|
5935
|
-
function _pendingFilePath(spaceId) {
|
|
5936
|
-
|
|
7759
|
+
async function _pendingFilePath(spaceId) {
|
|
7760
|
+
const { tmpdir } = await import("os");
|
|
7761
|
+
const { join: join4 } = await import("path");
|
|
7762
|
+
return join4(tmpdir(), `cue-sync-pending-${spaceId}.json`);
|
|
5937
7763
|
}
|
|
5938
7764
|
async function _loadPending(spaceId) {
|
|
5939
7765
|
if (typeof window !== "undefined") {
|
|
@@ -5941,7 +7767,7 @@ async function _loadPending(spaceId) {
|
|
|
5941
7767
|
return raw ? JSON.parse(raw) : null;
|
|
5942
7768
|
}
|
|
5943
7769
|
try {
|
|
5944
|
-
const raw = await (
|
|
7770
|
+
const raw = await (await import("fs/promises")).readFile(await _pendingFilePath(spaceId), "utf-8");
|
|
5945
7771
|
return JSON.parse(raw);
|
|
5946
7772
|
} catch {
|
|
5947
7773
|
return null;
|
|
@@ -5953,7 +7779,7 @@ async function _savePending(batch) {
|
|
|
5953
7779
|
window.localStorage.setItem(`${PENDING_LS_PREFIX}${batch.spaceId}`, data);
|
|
5954
7780
|
return;
|
|
5955
7781
|
}
|
|
5956
|
-
await (
|
|
7782
|
+
await (await import("fs/promises")).writeFile(await _pendingFilePath(batch.spaceId), data, "utf-8");
|
|
5957
7783
|
}
|
|
5958
7784
|
async function _clearPending(spaceId) {
|
|
5959
7785
|
if (typeof window !== "undefined") {
|
|
@@ -5961,7 +7787,7 @@ async function _clearPending(spaceId) {
|
|
|
5961
7787
|
return;
|
|
5962
7788
|
}
|
|
5963
7789
|
try {
|
|
5964
|
-
await (
|
|
7790
|
+
await (await import("fs/promises")).unlink(await _pendingFilePath(spaceId));
|
|
5965
7791
|
} catch {
|
|
5966
7792
|
}
|
|
5967
7793
|
}
|
|
@@ -5977,18 +7803,30 @@ var CueSyncApi = class {
|
|
|
5977
7803
|
_pendingItems = [];
|
|
5978
7804
|
_pendingSpaceId = null;
|
|
5979
7805
|
_flushTimer = null;
|
|
7806
|
+
_legacy = false;
|
|
5980
7807
|
/** @internal Injected by CueApi after construction to avoid circular dependency. */
|
|
5981
7808
|
_bindApi(api) {
|
|
5982
7809
|
this._api = api;
|
|
5983
7810
|
}
|
|
5984
7811
|
/**
|
|
5985
|
-
*
|
|
5986
|
-
*
|
|
7812
|
+
* Initialises browser-mode sync for a project space.
|
|
7813
|
+
* - Flushes any metadata items that were queued but not sent in a previous session
|
|
7814
|
+
* (persisted in `localStorage`).
|
|
7815
|
+
* - Starts the 60-second periodic flush timer.
|
|
7816
|
+
*
|
|
7817
|
+
* Call this once when the file manager component is created (or when the active
|
|
7818
|
+
* project changes) so that interrupted uploads are recovered immediately.
|
|
5987
7819
|
*/
|
|
5988
|
-
async
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
7820
|
+
async initBrowserSync(spaceId) {
|
|
7821
|
+
await this._initPendingBatch(spaceId);
|
|
7822
|
+
}
|
|
7823
|
+
/**
|
|
7824
|
+
* Flushes any pending file-location metadata from a previously interrupted sync.
|
|
7825
|
+
* Safe to call even when there are no new files to upload (e.g. when the process
|
|
7826
|
+
* died after uploading to blob storage but before the commands-API batch POST).
|
|
7827
|
+
*/
|
|
7828
|
+
async flushPendingMetadata(spaceId, verbose, legacy) {
|
|
7829
|
+
this._legacy = legacy ?? false;
|
|
5992
7830
|
const existing = await _loadPending(spaceId);
|
|
5993
7831
|
if (!existing || existing.items.length === 0)
|
|
5994
7832
|
return;
|
|
@@ -5998,7 +7836,7 @@ var CueSyncApi = class {
|
|
|
5998
7836
|
try {
|
|
5999
7837
|
this._pendingSpaceId = spaceId;
|
|
6000
7838
|
this._pendingItems = [];
|
|
6001
|
-
await this._flushBatch(existing.items, spaceId,
|
|
7839
|
+
await this._flushBatch(existing.items, spaceId, verbose);
|
|
6002
7840
|
console.info("Metadata uploaded \u2705");
|
|
6003
7841
|
} catch (err) {
|
|
6004
7842
|
throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6019,7 +7857,7 @@ var CueSyncApi = class {
|
|
|
6019
7857
|
const tier = project?.projectSettings?.tier ?? "l";
|
|
6020
7858
|
const [remoteFiles, consumption, creditMap, tierNames] = await Promise.all([
|
|
6021
7859
|
this._listRemoteFiles(graph, spaceId, providerId, verbose),
|
|
6022
|
-
this._api?.getConsumption(spaceId
|
|
7860
|
+
this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance")),
|
|
6023
7861
|
this._fetchUnitCreditMap(verbose),
|
|
6024
7862
|
this._fetchTierNames()
|
|
6025
7863
|
]);
|
|
@@ -6053,18 +7891,17 @@ var CueSyncApi = class {
|
|
|
6053
7891
|
};
|
|
6054
7892
|
}
|
|
6055
7893
|
async sync(localFiles, options) {
|
|
6056
|
-
const { spaceId, providerId, userId, verbose, onProgress } = options;
|
|
7894
|
+
const { spaceId, providerId, userId, verbose, onProgress, legacy } = options;
|
|
7895
|
+
this._legacy = legacy ?? false;
|
|
6057
7896
|
const token = await this._auth.getToken();
|
|
6058
7897
|
if (!token)
|
|
6059
7898
|
throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
6060
7899
|
const graph = await this._getOrCreateGraph(spaceId, token);
|
|
6061
7900
|
if (verbose)
|
|
6062
7901
|
console.info("Listing remote files \u23F3");
|
|
6063
|
-
const project = await this._projects.getProject(spaceId);
|
|
6064
|
-
const tier = project?.projectSettings?.tier ?? "l";
|
|
6065
7902
|
const [remoteFiles, consumption] = await Promise.all([
|
|
6066
7903
|
this._listRemoteFiles(graph, spaceId, providerId, verbose),
|
|
6067
|
-
this._api?.getConsumption(spaceId
|
|
7904
|
+
this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
|
|
6068
7905
|
]);
|
|
6069
7906
|
const { unitsAvailable } = consumption;
|
|
6070
7907
|
const report = await compareLocalRemote(localFiles, remoteFiles);
|
|
@@ -6089,7 +7926,7 @@ var CueSyncApi = class {
|
|
|
6089
7926
|
);
|
|
6090
7927
|
}
|
|
6091
7928
|
}
|
|
6092
|
-
await this._initPendingBatch(spaceId,
|
|
7929
|
+
await this._initPendingBatch(spaceId, verbose);
|
|
6093
7930
|
if (verbose && toUpload.length)
|
|
6094
7931
|
console.info("Syncing missing files \u23F3");
|
|
6095
7932
|
for (const file of toUpload) {
|
|
@@ -6103,10 +7940,10 @@ var CueSyncApi = class {
|
|
|
6103
7940
|
);
|
|
6104
7941
|
if (!rawMeta.blob_name)
|
|
6105
7942
|
throw new Error(`blob_name missing for ${file.relativePath}`);
|
|
6106
|
-
const fileBuffer =
|
|
7943
|
+
const fileBuffer = file.data ?? new Uint8Array(await _readNodeFile(file.fullPath));
|
|
6107
7944
|
await this._blob.uploadRaw(
|
|
6108
7945
|
rawMeta.blob_name,
|
|
6109
|
-
|
|
7946
|
+
fileBuffer,
|
|
6110
7947
|
rawMeta
|
|
6111
7948
|
);
|
|
6112
7949
|
await this._queueFileLocation({
|
|
@@ -6144,7 +7981,7 @@ var CueSyncApi = class {
|
|
|
6144
7981
|
}
|
|
6145
7982
|
await this._drainPending(verbose);
|
|
6146
7983
|
this._stopFlushTimer();
|
|
6147
|
-
const postSyncConsumption = await (this._api?.getConsumption(spaceId
|
|
7984
|
+
const postSyncConsumption = await (this._api?.getConsumption(spaceId) ?? Promise.resolve({ creditsAvailable: 0 }));
|
|
6148
7985
|
return {
|
|
6149
7986
|
syncCount,
|
|
6150
7987
|
syncSize,
|
|
@@ -6237,7 +8074,11 @@ WHERE {
|
|
|
6237
8074
|
}
|
|
6238
8075
|
return map;
|
|
6239
8076
|
}
|
|
6240
|
-
async _initPendingBatch(spaceId,
|
|
8077
|
+
async _initPendingBatch(spaceId, verbose) {
|
|
8078
|
+
if (this._flushTimer !== null) {
|
|
8079
|
+
clearInterval(this._flushTimer);
|
|
8080
|
+
this._flushTimer = null;
|
|
8081
|
+
}
|
|
6241
8082
|
this._pendingSpaceId = spaceId;
|
|
6242
8083
|
this._pendingItems = [];
|
|
6243
8084
|
const existing = await _loadPending(spaceId);
|
|
@@ -6246,7 +8087,7 @@ WHERE {
|
|
|
6246
8087
|
if (verbose)
|
|
6247
8088
|
console.info(`Flushing ${existing.items.length} pending file location(s) from previous sync \u23F3`);
|
|
6248
8089
|
try {
|
|
6249
|
-
await this._flushBatch(existing.items, spaceId,
|
|
8090
|
+
await this._flushBatch(existing.items, spaceId, verbose);
|
|
6250
8091
|
console.info("Metadata uploaded \u2705");
|
|
6251
8092
|
} catch (err) {
|
|
6252
8093
|
throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6268,22 +8109,29 @@ WHERE {
|
|
|
6268
8109
|
await _savePending({ spaceId: this._pendingSpaceId, items: this._pendingItems });
|
|
6269
8110
|
}
|
|
6270
8111
|
}
|
|
8112
|
+
/**
|
|
8113
|
+
* Flush all queued file-location items to the commands API in a single batch.
|
|
8114
|
+
* Call this once after a group of `syncBrowserFile` calls completes so that
|
|
8115
|
+
* all items are sent together rather than one POST per file.
|
|
8116
|
+
*/
|
|
8117
|
+
async drainPending() {
|
|
8118
|
+
await this._drainPending();
|
|
8119
|
+
}
|
|
6271
8120
|
async _drainPending(verbose) {
|
|
6272
8121
|
if (!this._pendingSpaceId || this._pendingItems.length === 0)
|
|
6273
8122
|
return;
|
|
6274
|
-
|
|
6275
|
-
if (!token)
|
|
8123
|
+
if (!this._auth.currentUser)
|
|
6276
8124
|
return;
|
|
6277
|
-
await this._flushBatch(this._pendingItems, this._pendingSpaceId,
|
|
8125
|
+
await this._flushBatch(this._pendingItems, this._pendingSpaceId, verbose);
|
|
6278
8126
|
}
|
|
6279
|
-
async _flushBatch(items, spaceId,
|
|
8127
|
+
async _flushBatch(items, spaceId, verbose) {
|
|
6280
8128
|
const snapshot = [...items];
|
|
6281
8129
|
if (this._pendingSpaceId === spaceId)
|
|
6282
8130
|
this._pendingItems = [];
|
|
6283
8131
|
await _clearPending(spaceId);
|
|
6284
8132
|
try {
|
|
6285
8133
|
for (let i = 0; i < snapshot.length; i += FSS_BATCH_CHUNK_SIZE) {
|
|
6286
|
-
await this._postFssBatch(snapshot.slice(i, i + FSS_BATCH_CHUNK_SIZE), spaceId
|
|
8134
|
+
await this._postFssBatch(snapshot.slice(i, i + FSS_BATCH_CHUNK_SIZE), spaceId);
|
|
6287
8135
|
}
|
|
6288
8136
|
if (verbose)
|
|
6289
8137
|
console.info(`Wrote ${snapshot.length} file location(s) to commands API \u2705`);
|
|
@@ -6294,15 +8142,15 @@ WHERE {
|
|
|
6294
8142
|
throw err;
|
|
6295
8143
|
}
|
|
6296
8144
|
}
|
|
6297
|
-
async _postFssBatch(items, spaceId
|
|
8145
|
+
async _postFssBatch(items, spaceId) {
|
|
6298
8146
|
const controller = new AbortController();
|
|
6299
8147
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
8148
|
+
const url = this._legacy ? `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}?blob=true` : `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`;
|
|
6300
8149
|
let response;
|
|
6301
8150
|
try {
|
|
6302
|
-
response = await
|
|
8151
|
+
response = await this._auth.authenticatedFetch(url, {
|
|
6303
8152
|
method: "POST",
|
|
6304
8153
|
headers: {
|
|
6305
|
-
Authorization: `Bearer ${token}`,
|
|
6306
8154
|
"Content-Type": "application/json",
|
|
6307
8155
|
"x-project-id": spaceId
|
|
6308
8156
|
},
|
|
@@ -6346,7 +8194,8 @@ WHERE {
|
|
|
6346
8194
|
const entries = await Promise.all(
|
|
6347
8195
|
batch.map(async (f) => ({
|
|
6348
8196
|
originalPath: f.relativePath,
|
|
6349
|
-
data
|
|
8197
|
+
// Use pre-loaded data if available (browser), otherwise read from disk (Node.js).
|
|
8198
|
+
data: f.data ?? new Uint8Array(await _readNodeFile(f.fullPath))
|
|
6350
8199
|
}))
|
|
6351
8200
|
);
|
|
6352
8201
|
const records = _scanFn(entries);
|
|
@@ -6363,6 +8212,94 @@ WHERE {
|
|
|
6363
8212
|
}
|
|
6364
8213
|
return Array.from(merged.values());
|
|
6365
8214
|
}
|
|
8215
|
+
/**
|
|
8216
|
+
* Compute the credit cost for a set of local files without uploading anything.
|
|
8217
|
+
* Intended for browser use where the full {@link previewSync} (which requires a
|
|
8218
|
+
* remote file listing) would be too heavy for a quick estimate.
|
|
8219
|
+
*
|
|
8220
|
+
* @param localFiles - Files to analyse. Each entry must carry `data` when
|
|
8221
|
+
* called from a browser context.
|
|
8222
|
+
* @param spaceId - Project/space identifier used to fetch the tier settings.
|
|
8223
|
+
* @returns Per-extension cost breakdown and the number of credits currently
|
|
8224
|
+
* available in the project.
|
|
8225
|
+
*/
|
|
8226
|
+
async computeCredits(localFiles, spaceId) {
|
|
8227
|
+
const project = await this._projects.getProject(spaceId);
|
|
8228
|
+
const tier = project?.projectSettings?.tier ?? "l";
|
|
8229
|
+
console.info(`Computing credit cost for ${localFiles.length} file(s) using tier "${tier}"...`);
|
|
8230
|
+
const consumptionPromise = (this._api?.getConsumption(spaceId) ?? Promise.resolve({ creditsAvailable: 0, unitsAvailable: 0 })).then((r) => {
|
|
8231
|
+
console.info("[computeCredits] getConsumption resolved:", r);
|
|
8232
|
+
return r;
|
|
8233
|
+
}).catch((err) => {
|
|
8234
|
+
console.warn("[computeCredits] getConsumption failed, defaulting to 0:", err?.message ?? err);
|
|
8235
|
+
return { creditsAvailable: 0, unitsAvailable: 0 };
|
|
8236
|
+
});
|
|
8237
|
+
const creditMapPromise = this._fetchUnitCreditMap().then((m) => {
|
|
8238
|
+
console.info("[computeCredits] creditMap resolved, keys:", Object.keys(m));
|
|
8239
|
+
return m;
|
|
8240
|
+
}).catch((err) => {
|
|
8241
|
+
console.warn("[computeCredits] fetchUnitCreditMap failed, using default rates:", err?.message ?? err);
|
|
8242
|
+
return {};
|
|
8243
|
+
});
|
|
8244
|
+
const scanPromise = localFiles.length > 0 ? (console.info(`[computeCredits] starting WASM scan of ${localFiles.length} file(s)...`), this.scanCost(localFiles).then((r) => {
|
|
8245
|
+
console.info(`[computeCredits] WASM scan done: ${r.length} ext(s)`);
|
|
8246
|
+
return r;
|
|
8247
|
+
})) : Promise.resolve([]);
|
|
8248
|
+
const [costRecords, creditMap, consumption] = await Promise.all([
|
|
8249
|
+
scanPromise,
|
|
8250
|
+
creditMapPromise,
|
|
8251
|
+
consumptionPromise
|
|
8252
|
+
]);
|
|
8253
|
+
console.info(`[computeCredits] all resolved \u2014 ${costRecords.length} ext(s), creditsAvailable: ${consumption.creditsAvailable}`);
|
|
8254
|
+
let creditsToConsume = 0;
|
|
8255
|
+
for (const r of costRecords) {
|
|
8256
|
+
const tierMap = creditMap[tier];
|
|
8257
|
+
const creditPerUnit = tierMap?.[r.ext] ?? 1;
|
|
8258
|
+
const credits = r.units * creditPerUnit;
|
|
8259
|
+
creditsToConsume += credits;
|
|
8260
|
+
r.credits = Math.round(credits);
|
|
8261
|
+
}
|
|
8262
|
+
return {
|
|
8263
|
+
costRecords,
|
|
8264
|
+
creditsToConsume: Math.round(creditsToConsume),
|
|
8265
|
+
creditsAvailable: consumption.creditsAvailable
|
|
8266
|
+
};
|
|
8267
|
+
}
|
|
8268
|
+
/**
|
|
8269
|
+
* Upload a single browser-supplied file and write its metadata to the knowledge graph.
|
|
8270
|
+
*
|
|
8271
|
+
* Unlike {@link sync} (which performs a full remote comparison), this method is
|
|
8272
|
+
* designed for the web file-manager flow where the user has already confirmed the
|
|
8273
|
+
* upload via the credit modal. The file's binary data must be provided in
|
|
8274
|
+
* `file.data`; the `file.fullPath` field is ignored.
|
|
8275
|
+
*
|
|
8276
|
+
* Cancellation is supported via `options.signal`. Aborting the signal cancels
|
|
8277
|
+
* the Firebase Storage upload; metadata is never written for a cancelled upload.
|
|
8278
|
+
*
|
|
8279
|
+
* @param file - `LocalFile` with `data` populated (e.g. from `File.arrayBuffer()`).
|
|
8280
|
+
* @param options - Upload options including project/provider/user context and an
|
|
8281
|
+
* optional `AbortSignal` for cancellation and `onProgress` for tracking.
|
|
8282
|
+
*/
|
|
8283
|
+
async syncBrowserFile(file, options) {
|
|
8284
|
+
const { spaceId, providerId, userId, signal, onProgress } = options;
|
|
8285
|
+
if (!file.data) {
|
|
8286
|
+
throw new Error("syncBrowserFile requires file.data (Uint8Array). Read the file with File.arrayBuffer() first.");
|
|
8287
|
+
}
|
|
8288
|
+
const rawMeta = uploadedFileMetadata(file.relativePath, spaceId, userId, file.md5, providerId);
|
|
8289
|
+
if (!rawMeta.blob_name)
|
|
8290
|
+
throw new Error(`blob_name missing for ${file.relativePath}`);
|
|
8291
|
+
await this._blob.uploadRaw(
|
|
8292
|
+
rawMeta.blob_name,
|
|
8293
|
+
file.data,
|
|
8294
|
+
rawMeta,
|
|
8295
|
+
3,
|
|
8296
|
+
signal,
|
|
8297
|
+
onProgress
|
|
8298
|
+
);
|
|
8299
|
+
await this._queueFileLocation(
|
|
8300
|
+
{ relativePath: file.relativePath, md5: file.md5, size: file.size, providerId, fileContentExists: false }
|
|
8301
|
+
);
|
|
8302
|
+
}
|
|
6366
8303
|
async _fetchTierNames() {
|
|
6367
8304
|
try {
|
|
6368
8305
|
const text = await this._blob.downloadPublic("tier-names.json");
|
|
@@ -6394,28 +8331,201 @@ WHERE {
|
|
|
6394
8331
|
}
|
|
6395
8332
|
};
|
|
6396
8333
|
|
|
8334
|
+
// libs/js/cue-sdk/src/lib/cue.ts
|
|
8335
|
+
var ENDPOINTS = {
|
|
8336
|
+
production: {
|
|
8337
|
+
gatewayUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app",
|
|
8338
|
+
tokenUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token",
|
|
8339
|
+
authEmulatorUrl: "http://localhost:9099",
|
|
8340
|
+
storageEmulatorHost: "localhost",
|
|
8341
|
+
storageEmulatorPort: 9199,
|
|
8342
|
+
firestoreEmulatorHost: "localhost",
|
|
8343
|
+
firestoreEmulatorPort: 8080
|
|
8344
|
+
},
|
|
8345
|
+
emulator: {
|
|
8346
|
+
gatewayUrl: "http://localhost:18093",
|
|
8347
|
+
tokenUrl: "http://localhost:18093/token",
|
|
8348
|
+
authEmulatorUrl: "http://localhost:9099",
|
|
8349
|
+
storageEmulatorHost: "localhost",
|
|
8350
|
+
storageEmulatorPort: 9199,
|
|
8351
|
+
firestoreEmulatorHost: "localhost",
|
|
8352
|
+
firestoreEmulatorPort: 8080
|
|
8353
|
+
}
|
|
8354
|
+
};
|
|
8355
|
+
var Cue = class _Cue {
|
|
8356
|
+
auth;
|
|
8357
|
+
api;
|
|
8358
|
+
projects;
|
|
8359
|
+
profile;
|
|
8360
|
+
privileges;
|
|
8361
|
+
cache;
|
|
8362
|
+
_app;
|
|
8363
|
+
_endpoints;
|
|
8364
|
+
_isEmulator;
|
|
8365
|
+
constructor(config = {}) {
|
|
8366
|
+
const usingDefaults = !config.apiKey && !config.appId && !config.measurementId;
|
|
8367
|
+
if (usingDefaults) {
|
|
8368
|
+
console.warn(
|
|
8369
|
+
"Using default SDK app settings. Contact QAECY for your own configuration for any production code."
|
|
8370
|
+
);
|
|
8371
|
+
}
|
|
8372
|
+
const apiKey = config.apiKey ?? DEFAULT_SDK_CONFIG.apiKey;
|
|
8373
|
+
const appId = config.appId ?? DEFAULT_SDK_CONFIG.appId;
|
|
8374
|
+
const measurementId = config.measurementId ?? DEFAULT_SDK_CONFIG.measurementId;
|
|
8375
|
+
const env = config.environment ?? "production";
|
|
8376
|
+
this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
|
|
8377
|
+
this._isEmulator = env === "emulator";
|
|
8378
|
+
this._app = (0, import_app2.getApps)().find((a5) => a5.name === "[DEFAULT]") ?? (0, import_app2.initializeApp)({
|
|
8379
|
+
apiKey,
|
|
8380
|
+
appId,
|
|
8381
|
+
measurementId,
|
|
8382
|
+
authDomain: `${FIREBASE_PROJECT_ID}.firebaseapp.com`,
|
|
8383
|
+
projectId: FIREBASE_PROJECT_ID,
|
|
8384
|
+
messagingSenderId: FIREBASE_SENDER_ID
|
|
8385
|
+
});
|
|
8386
|
+
this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
|
|
8387
|
+
this.projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
|
|
8388
|
+
this.api = this._buildApi(this.projects);
|
|
8389
|
+
this.profile = new CueProfile(
|
|
8390
|
+
this.auth,
|
|
8391
|
+
this._app,
|
|
8392
|
+
this._isEmulator,
|
|
8393
|
+
this._endpoints.firestoreEmulatorHost,
|
|
8394
|
+
this._endpoints.firestoreEmulatorPort
|
|
8395
|
+
);
|
|
8396
|
+
this.privileges = new CuePrivileges(this.auth.isSuperAdmin);
|
|
8397
|
+
const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
|
|
8398
|
+
if (this._isEmulator) {
|
|
8399
|
+
(0, import_storage5.connectStorageEmulator)(storagePersistence, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
|
|
8400
|
+
}
|
|
8401
|
+
this.cache = new CueCache(storagePersistence);
|
|
8402
|
+
}
|
|
8403
|
+
/**
|
|
8404
|
+
* Create a `Cue` instance from an already-initialized Firebase app.
|
|
8405
|
+
*
|
|
8406
|
+
* Use this when the host application (e.g. cue-portal via `CueFirebase`) has
|
|
8407
|
+
* already called `initializeApp()`. Reusing the same `FirebaseApp` means the
|
|
8408
|
+
* SDK shares the same auth state and Firestore instance — no second login is
|
|
8409
|
+
* needed and `getToken()` returns the portal user's token directly.
|
|
8410
|
+
*
|
|
8411
|
+
* Emulator connections are skipped because the host app already set them up.
|
|
8412
|
+
*
|
|
8413
|
+
* @example
|
|
8414
|
+
* // In an Angular service after CueFirebase is ready:
|
|
8415
|
+
* const cue = Cue.fromApp(CueFirebase.getInstance().app!, {
|
|
8416
|
+
* environment: 'emulator',
|
|
8417
|
+
* });
|
|
8418
|
+
*/
|
|
8419
|
+
static fromApp(app, config = {}) {
|
|
8420
|
+
const env = config.environment ?? "production";
|
|
8421
|
+
const endpoints = { ...ENDPOINTS[env], ...config.endpoints };
|
|
8422
|
+
const auth = new CueAuth(app, false, endpoints);
|
|
8423
|
+
const projects = new CueProjects(auth, app, false, endpoints);
|
|
8424
|
+
const storageRaw = (0, import_storage5.getStorage)(app, BUCKET_RAW2);
|
|
8425
|
+
const storageProcessed = (0, import_storage5.getStorage)(app, BUCKET_PROCESSED2);
|
|
8426
|
+
const storagePublic = (0, import_storage5.getStorage)(app, BUCKET_PUBLIC2);
|
|
8427
|
+
const storageLogs = (0, import_storage5.getStorage)(app, BUCKET_LOGS2);
|
|
8428
|
+
const storageChatSessions = (0, import_storage5.getStorage)(app, BUCKET_CHAT_SESSIONS2);
|
|
8429
|
+
const storagePersistence = (0, import_storage5.getStorage)(app, BUCKET_PERSISTENCE2);
|
|
8430
|
+
const blob = new CueBlobStorage({
|
|
8431
|
+
storageRaw,
|
|
8432
|
+
storageProcessed,
|
|
8433
|
+
storagePublic,
|
|
8434
|
+
storageLogs,
|
|
8435
|
+
storageChatSessions,
|
|
8436
|
+
storagePersistence
|
|
8437
|
+
});
|
|
8438
|
+
const syncApi = new CueSyncApi(auth, projects, blob, endpoints.gatewayUrl);
|
|
8439
|
+
const api = new CueApi(auth, endpoints.gatewayUrl, projects, syncApi);
|
|
8440
|
+
syncApi._bindApi(api);
|
|
8441
|
+
const profile = new CueProfile(auth, app, false, endpoints.firestoreEmulatorHost, endpoints.firestoreEmulatorPort);
|
|
8442
|
+
const instance = Object.create(_Cue.prototype);
|
|
8443
|
+
const privileges = new CuePrivileges(auth.isSuperAdmin);
|
|
8444
|
+
const cache = new CueCache(storagePersistence);
|
|
8445
|
+
Object.assign(instance, {
|
|
8446
|
+
_app: app,
|
|
8447
|
+
_endpoints: endpoints,
|
|
8448
|
+
_isEmulator: env === "emulator",
|
|
8449
|
+
auth,
|
|
8450
|
+
api,
|
|
8451
|
+
projects,
|
|
8452
|
+
profile,
|
|
8453
|
+
privileges,
|
|
8454
|
+
cache
|
|
8455
|
+
});
|
|
8456
|
+
return instance;
|
|
8457
|
+
}
|
|
8458
|
+
/** Override in subclasses to provide a custom CueApi (e.g. with sync). */
|
|
8459
|
+
_buildApi(projects) {
|
|
8460
|
+
return new CueApi(this.auth, this._endpoints.gatewayUrl, projects);
|
|
8461
|
+
}
|
|
8462
|
+
/** Convenience: get the current user's Firebase ID token */
|
|
8463
|
+
getToken(forceRefresh = false) {
|
|
8464
|
+
return this.auth.getToken(forceRefresh);
|
|
8465
|
+
}
|
|
8466
|
+
/**
|
|
8467
|
+
* Create a `CueProjectView` for the given project, wiring the SDK's query
|
|
8468
|
+
* cache automatically.
|
|
8469
|
+
*
|
|
8470
|
+
* The view auto-fetches the document overview and entity graph on construction
|
|
8471
|
+
* and exposes reactive signals for all project knowledge-graph state.
|
|
8472
|
+
*
|
|
8473
|
+
* @example
|
|
8474
|
+
* ```ts
|
|
8475
|
+
* const view = cue.createProjectView('my-project', { language: 'en' });
|
|
8476
|
+
* view.requestEntityData(['uuid1']);
|
|
8477
|
+
* const info = view.entityInfoMap.get()['uuid1'];
|
|
8478
|
+
* ```
|
|
8479
|
+
*/
|
|
8480
|
+
createProjectView(projectId, opts) {
|
|
8481
|
+
const queryCache = opts.queryCache ?? {
|
|
8482
|
+
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
8483
|
+
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
8484
|
+
};
|
|
8485
|
+
return new CueProjectView(this.api, projectId, { ...opts, queryCache });
|
|
8486
|
+
}
|
|
8487
|
+
};
|
|
8488
|
+
|
|
6397
8489
|
// libs/js/cue-sdk/src/lib/cue-node.ts
|
|
6398
|
-
var
|
|
6399
|
-
var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
|
|
6400
|
-
var BUCKET_PUBLIC2 = "cue_public_eu_west6";
|
|
8490
|
+
var import_storage6 = require("firebase/storage");
|
|
6401
8491
|
var CueNode = class extends Cue {
|
|
6402
8492
|
constructor(config) {
|
|
6403
8493
|
super(config);
|
|
6404
8494
|
}
|
|
6405
8495
|
_buildApi(projects) {
|
|
6406
|
-
const
|
|
6407
|
-
const
|
|
6408
|
-
const
|
|
8496
|
+
const storageChatSessions = (0, import_storage6.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
|
|
8497
|
+
const storageLogs = (0, import_storage6.getStorage)(this._app, BUCKET_LOGS2);
|
|
8498
|
+
const storageRaw = (0, import_storage6.getStorage)(this._app, BUCKET_RAW2);
|
|
8499
|
+
const storagePersistence = (0, import_storage6.getStorage)(this._app, BUCKET_PERSISTENCE2);
|
|
8500
|
+
const storageProcessed = (0, import_storage6.getStorage)(this._app, BUCKET_PROCESSED2);
|
|
8501
|
+
const storagePublic = (0, import_storage6.getStorage)(this._app, BUCKET_PUBLIC2);
|
|
6409
8502
|
if (this._isEmulator) {
|
|
6410
8503
|
const storageHost = this._endpoints.storageEmulatorHost;
|
|
6411
8504
|
const storagePort = this._endpoints.storageEmulatorPort;
|
|
6412
|
-
(0,
|
|
6413
|
-
(0,
|
|
6414
|
-
(0,
|
|
8505
|
+
(0, import_storage6.connectStorageEmulator)(storageRaw, storageHost, storagePort);
|
|
8506
|
+
(0, import_storage6.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
|
|
8507
|
+
(0, import_storage6.connectStorageEmulator)(storagePublic, storageHost, storagePort);
|
|
6415
8508
|
}
|
|
6416
|
-
const blob = new CueBlobStorage({
|
|
6417
|
-
|
|
6418
|
-
|
|
8509
|
+
const blob = new CueBlobStorage({
|
|
8510
|
+
storageChatSessions,
|
|
8511
|
+
storageLogs,
|
|
8512
|
+
storageRaw,
|
|
8513
|
+
storagePersistence,
|
|
8514
|
+
storageProcessed,
|
|
8515
|
+
storagePublic
|
|
8516
|
+
});
|
|
8517
|
+
const syncApi = new CueSyncApi(
|
|
8518
|
+
this.auth,
|
|
8519
|
+
projects,
|
|
8520
|
+
blob,
|
|
8521
|
+
this._endpoints.gatewayUrl
|
|
8522
|
+
);
|
|
8523
|
+
const api = new CueApi(
|
|
8524
|
+
this.auth,
|
|
8525
|
+
this._endpoints.gatewayUrl,
|
|
8526
|
+
projects,
|
|
8527
|
+
syncApi
|
|
8528
|
+
);
|
|
6419
8529
|
syncApi._bindApi(api);
|
|
6420
8530
|
return api;
|
|
6421
8531
|
}
|
|
@@ -6457,7 +8567,7 @@ async function compareHandler(options) {
|
|
|
6457
8567
|
await authenticate(emulators, space, options.key, verbose);
|
|
6458
8568
|
if (verbose)
|
|
6459
8569
|
console.info("Building compare base \u23F3");
|
|
6460
|
-
const qh = async (
|
|
8570
|
+
const qh = async (query4) => queryHandler(query4, space, emulators);
|
|
6461
8571
|
const [localFiles, remoteFiles] = await Promise.all([
|
|
6462
8572
|
listLocalFiles(
|
|
6463
8573
|
path,
|
|
@@ -6557,23 +8667,23 @@ async function compareHandler(options) {
|
|
|
6557
8667
|
}
|
|
6558
8668
|
|
|
6559
8669
|
// apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
|
|
6560
|
-
var
|
|
6561
|
-
var
|
|
6562
|
-
var
|
|
8670
|
+
var import_storage7 = require("firebase/storage");
|
|
8671
|
+
var import_path2 = require("path");
|
|
8672
|
+
var import_promises = require("fs/promises");
|
|
6563
8673
|
async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
|
|
6564
8674
|
const firebase = CueFirebase.getInstance();
|
|
6565
8675
|
const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
|
|
6566
8676
|
console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
|
|
6567
|
-
const listResult = await (0,
|
|
6568
|
-
const outputDir = (0,
|
|
6569
|
-
await (0,
|
|
8677
|
+
const listResult = await (0, import_storage7.listAll)((0, import_storage7.ref)(storage, subDir));
|
|
8678
|
+
const outputDir = (0, import_path2.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
|
|
8679
|
+
await (0, import_promises.mkdir)(outputDir, { recursive: true });
|
|
6570
8680
|
for (const fileRef of listResult.items) {
|
|
6571
8681
|
console.log(fileRef.name);
|
|
6572
8682
|
if (subDir && !fileRef.name.includes(subString))
|
|
6573
8683
|
continue;
|
|
6574
|
-
const bytes = await (0,
|
|
6575
|
-
const outputPath = (0,
|
|
6576
|
-
await (0,
|
|
8684
|
+
const bytes = await (0, import_storage7.getBytes)(fileRef);
|
|
8685
|
+
const outputPath = (0, import_path2.join)(outputDir, fileRef.name);
|
|
8686
|
+
await (0, import_promises.writeFile)(outputPath, Buffer.from(bytes));
|
|
6577
8687
|
console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
|
|
6578
8688
|
}
|
|
6579
8689
|
}
|
|
@@ -6598,12 +8708,12 @@ async function dumpProcessedHandler(options) {
|
|
|
6598
8708
|
}
|
|
6599
8709
|
|
|
6600
8710
|
// apps/desktop/cue-cli/src/helpers/graph-dump.ts
|
|
6601
|
-
var
|
|
8711
|
+
var import_fs = require("fs");
|
|
6602
8712
|
var import_stream = require("stream");
|
|
6603
|
-
var
|
|
6604
|
-
var
|
|
6605
|
-
var
|
|
6606
|
-
var
|
|
8713
|
+
var import_fs2 = require("fs");
|
|
8714
|
+
var import_zlib = require("zlib");
|
|
8715
|
+
var import_promises2 = require("stream/promises");
|
|
8716
|
+
var import_promises3 = require("fs/promises");
|
|
6607
8717
|
var import_auth9 = require("firebase/auth");
|
|
6608
8718
|
|
|
6609
8719
|
// libs/js/size-tools/src/lib/js-size-tools.ts
|
|
@@ -6623,7 +8733,7 @@ function humanFileSize(bytes, si = false, dp = 1) {
|
|
|
6623
8733
|
}
|
|
6624
8734
|
|
|
6625
8735
|
// apps/desktop/cue-cli/src/helpers/graph-dump.ts
|
|
6626
|
-
var
|
|
8736
|
+
var import_fs3 = require("fs");
|
|
6627
8737
|
async function dumpRdfGraphToFileJelly(spaceId, useEmulator = false, verbose = false) {
|
|
6628
8738
|
return dumpRdfGraphToFile(
|
|
6629
8739
|
spaceId,
|
|
@@ -6639,7 +8749,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6639
8749
|
if (verbose)
|
|
6640
8750
|
console.info(`Streaming RDF graph \u23F3`);
|
|
6641
8751
|
const filePath = outFile || `${spaceId}.nq`;
|
|
6642
|
-
if ((0,
|
|
8752
|
+
if ((0, import_fs3.existsSync)(filePath) || (0, import_fs3.existsSync)(`${filePath}.gz`)) {
|
|
6643
8753
|
if (verbose)
|
|
6644
8754
|
console.info(`File ${filePath} already exists, skipping download.`);
|
|
6645
8755
|
return mimeType === "application/n-quads" ? `${filePath}.gz` : filePath;
|
|
@@ -6656,7 +8766,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6656
8766
|
if (!res.ok || !res.body) {
|
|
6657
8767
|
throw new Error(`Failed to stream data: ${res.status} ${res.statusText}`);
|
|
6658
8768
|
}
|
|
6659
|
-
const fileStream = (0,
|
|
8769
|
+
const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
|
|
6660
8770
|
const nodeStream = import_stream.Readable.fromWeb(res.body);
|
|
6661
8771
|
let chunkCount = 0;
|
|
6662
8772
|
let totalSize = 0;
|
|
@@ -6683,7 +8793,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6683
8793
|
}
|
|
6684
8794
|
}, streamTimeout);
|
|
6685
8795
|
try {
|
|
6686
|
-
await (0,
|
|
8796
|
+
await (0, import_promises2.pipeline)(nodeStream, fileStream);
|
|
6687
8797
|
} finally {
|
|
6688
8798
|
if (hasDataTimeout) {
|
|
6689
8799
|
clearTimeout(hasDataTimeout);
|
|
@@ -6716,7 +8826,7 @@ async function dumpRdfGraphToFileConstruct(spaceId, useEmulator = false, verbose
|
|
|
6716
8826
|
WHERE { GRAPH ${graph} { ?s ?p ?o } }
|
|
6717
8827
|
`;
|
|
6718
8828
|
let offset = 0;
|
|
6719
|
-
const fileStream = (0,
|
|
8829
|
+
const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
|
|
6720
8830
|
let hasMore = true;
|
|
6721
8831
|
while (hasMore) {
|
|
6722
8832
|
const queries = Array.from(
|
|
@@ -6781,14 +8891,14 @@ async function retryWithBackoff(fn, maxRetries, delayMs, label) {
|
|
|
6781
8891
|
async function _doGzip(filePath) {
|
|
6782
8892
|
const gzFilePath = `${filePath}.gz`;
|
|
6783
8893
|
await new Promise((resolve2, reject) => {
|
|
6784
|
-
const gzip = (0,
|
|
6785
|
-
const source = (0,
|
|
6786
|
-
const dest = (0,
|
|
6787
|
-
(0,
|
|
8894
|
+
const gzip = (0, import_zlib.createGzip)();
|
|
8895
|
+
const source = (0, import_fs2.createReadStream)(filePath);
|
|
8896
|
+
const dest = (0, import_fs.createWriteStream)(gzFilePath);
|
|
8897
|
+
(0, import_promises2.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
|
|
6788
8898
|
});
|
|
6789
|
-
await (0,
|
|
8899
|
+
await (0, import_promises3.unlink)(filePath);
|
|
6790
8900
|
}
|
|
6791
|
-
async function _doQuery(
|
|
8901
|
+
async function _doQuery(query4, spaceId, url) {
|
|
6792
8902
|
const token = await (0, import_auth9.getAuth)().currentUser?.getIdToken();
|
|
6793
8903
|
try {
|
|
6794
8904
|
const res = await fetch(url, {
|
|
@@ -6799,7 +8909,7 @@ async function _doQuery(query3, spaceId, url) {
|
|
|
6799
8909
|
"Content-Type": "application/sparql-query",
|
|
6800
8910
|
Accept: "application/n-quads"
|
|
6801
8911
|
},
|
|
6802
|
-
body:
|
|
8912
|
+
body: query4
|
|
6803
8913
|
});
|
|
6804
8914
|
if (!res.ok) {
|
|
6805
8915
|
console.error(`Error: ${res.status} ${res.statusText}`);
|
|
@@ -6878,9 +8988,9 @@ function _getTemplate(partition, base = "fuseki-base-new/fuseki") {
|
|
|
6878
8988
|
|
|
6879
8989
|
// apps/desktop/cue-cli/src/helpers/graph-upload.ts
|
|
6880
8990
|
var import_auth11 = require("firebase/auth");
|
|
6881
|
-
var
|
|
8991
|
+
var import_fs4 = require("fs");
|
|
6882
8992
|
async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
|
|
6883
|
-
const stream = (0,
|
|
8993
|
+
const stream = (0, import_fs4.createReadStream)(filePath);
|
|
6884
8994
|
const token = await (0, import_auth11.getAuth)().currentUser?.getIdToken();
|
|
6885
8995
|
const dataUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", "/data");
|
|
6886
8996
|
const headers = {
|
|
@@ -6907,7 +9017,7 @@ async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads"
|
|
|
6907
9017
|
|
|
6908
9018
|
// apps/desktop/cue-cli/src/cue-cli-dump.ts
|
|
6909
9019
|
async function dumpHandler(options) {
|
|
6910
|
-
const { space, verbose, emulators, jelly, query:
|
|
9020
|
+
const { space, verbose, emulators, jelly, query: query4, load } = options;
|
|
6911
9021
|
try {
|
|
6912
9022
|
const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
|
|
6913
9023
|
if (!isSuperAdmin) {
|
|
@@ -6928,7 +9038,7 @@ async function dumpHandler(options) {
|
|
|
6928
9038
|
} else {
|
|
6929
9039
|
if (verbose)
|
|
6930
9040
|
console.time("Downloaded and zipped graph \u2705");
|
|
6931
|
-
if (
|
|
9041
|
+
if (query4) {
|
|
6932
9042
|
if (verbose)
|
|
6933
9043
|
console.info("Setting: Construct query");
|
|
6934
9044
|
filePath = await dumpRdfGraphToFileConstruct(space, emulators, verbose);
|
|
@@ -6965,16 +9075,16 @@ async function dumpHandler(options) {
|
|
|
6965
9075
|
}
|
|
6966
9076
|
|
|
6967
9077
|
// apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
|
|
6968
|
-
var
|
|
9078
|
+
var import_storage8 = require("firebase/storage");
|
|
6969
9079
|
async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
6970
9080
|
const firebase = CueFirebase.getInstance();
|
|
6971
9081
|
const storage = firebase.storageProcessed;
|
|
6972
9082
|
console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
|
|
6973
|
-
const listResult = await (0,
|
|
9083
|
+
const listResult = await (0, import_storage8.listAll)((0, import_storage8.ref)(storage, `${space}/triples`));
|
|
6974
9084
|
for (const fileRef of listResult.items) {
|
|
6975
9085
|
if (subString && !fileRef.name.match(subString))
|
|
6976
9086
|
continue;
|
|
6977
|
-
const stream = await (0,
|
|
9087
|
+
const stream = await (0, import_storage8.getStream)(fileRef);
|
|
6978
9088
|
const reader = stream.getReader();
|
|
6979
9089
|
const chunks = [];
|
|
6980
9090
|
let done = false;
|
|
@@ -6998,13 +9108,13 @@ async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
|
6998
9108
|
const buffer = Buffer.from(fileContent, "utf8");
|
|
6999
9109
|
let existingMetadata = {};
|
|
7000
9110
|
try {
|
|
7001
|
-
existingMetadata = await (0,
|
|
9111
|
+
existingMetadata = await (0, import_storage8.getMetadata)(fileRef);
|
|
7002
9112
|
} catch (err) {
|
|
7003
9113
|
console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
|
|
7004
9114
|
}
|
|
7005
9115
|
const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
|
|
7006
9116
|
const metadata = { customMetadata };
|
|
7007
|
-
await (0,
|
|
9117
|
+
await (0, import_storage8.uploadBytesResumable)((0, import_storage8.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
|
|
7008
9118
|
console.log(`Fixed ${fileRef.name} \u2705`);
|
|
7009
9119
|
} else {
|
|
7010
9120
|
console.log(`No changes for ${fileRef.name}`);
|
|
@@ -7031,10 +9141,11 @@ async function repairTtlHandler(options) {
|
|
|
7031
9141
|
}
|
|
7032
9142
|
|
|
7033
9143
|
// apps/desktop/cue-cli/src/cue-cli-sync.ts
|
|
7034
|
-
var
|
|
7035
|
-
var
|
|
7036
|
-
var
|
|
9144
|
+
var import_promises4 = require("fs/promises");
|
|
9145
|
+
var import_fs5 = require("fs");
|
|
9146
|
+
var import_path3 = require("path");
|
|
7037
9147
|
var readline = __toESM(require("readline"));
|
|
9148
|
+
init_src();
|
|
7038
9149
|
function askConfirm(question) {
|
|
7039
9150
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7040
9151
|
return new Promise((resolve2) => {
|
|
@@ -7045,7 +9156,7 @@ function askConfirm(question) {
|
|
|
7045
9156
|
});
|
|
7046
9157
|
}
|
|
7047
9158
|
async function syncHandler(options) {
|
|
7048
|
-
const { space, path, verbose, provider, emulators, zip } = options;
|
|
9159
|
+
const { space, path, verbose, provider, emulators, zip, legacy } = options;
|
|
7049
9160
|
try {
|
|
7050
9161
|
const cue = new CueNode({
|
|
7051
9162
|
apiKey: FIREBASE_CONFIG().apiKey,
|
|
@@ -7063,15 +9174,15 @@ async function syncHandler(options) {
|
|
|
7063
9174
|
}
|
|
7064
9175
|
if (verbose)
|
|
7065
9176
|
console.info("Building sync base \u23F3");
|
|
7066
|
-
const resolvedPath = (0,
|
|
7067
|
-
const pathStat = await (0,
|
|
9177
|
+
const resolvedPath = (0, import_path3.resolve)(path);
|
|
9178
|
+
const pathStat = await (0, import_promises4.stat)(resolvedPath);
|
|
7068
9179
|
const isFile = pathStat.isFile();
|
|
7069
9180
|
let localFiles;
|
|
7070
9181
|
if (isFile) {
|
|
7071
9182
|
if (verbose)
|
|
7072
9183
|
console.info(`Path is a file, syncing single file: ${resolvedPath}`);
|
|
7073
|
-
const md5 = await fromReadStream((0,
|
|
7074
|
-
const relativePath = (0,
|
|
9184
|
+
const md5 = await fromReadStream((0, import_fs5.createReadStream)(resolvedPath));
|
|
9185
|
+
const relativePath = (0, import_path3.basename)(resolvedPath);
|
|
7075
9186
|
const contentUUID = contextBasedGuid(md5);
|
|
7076
9187
|
const locationUUID = generateFileUUID(relativePath, provider);
|
|
7077
9188
|
localFiles = [
|
|
@@ -7132,7 +9243,7 @@ async function syncHandler(options) {
|
|
|
7132
9243
|
console.info(` Credits required: ${Math.round(preview.creditsToConsume)}`);
|
|
7133
9244
|
console.info(` Credits available: ${Math.round(preview.creditsAvailable)}
|
|
7134
9245
|
`);
|
|
7135
|
-
await cue.api.sync.flushPendingMetadata(space, verbose);
|
|
9246
|
+
await cue.api.sync.flushPendingMetadata(space, verbose, legacy);
|
|
7136
9247
|
if (preview.filesToUpload === 0) {
|
|
7137
9248
|
console.info("Everything is already synced.");
|
|
7138
9249
|
process.exit(0);
|
|
@@ -7153,6 +9264,7 @@ async function syncHandler(options) {
|
|
|
7153
9264
|
providerId: provider,
|
|
7154
9265
|
userId: user.uid,
|
|
7155
9266
|
verbose,
|
|
9267
|
+
legacy,
|
|
7156
9268
|
onProgress: ({ percent, syncCount, totalCount }) => {
|
|
7157
9269
|
const filled = Math.round(percent / 5);
|
|
7158
9270
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
@@ -7194,8 +9306,8 @@ async function syncHandler(options) {
|
|
|
7194
9306
|
}
|
|
7195
9307
|
|
|
7196
9308
|
// apps/desktop/cue-cli/src/cue-cli-util-remove-rdf-star.ts
|
|
7197
|
-
var
|
|
7198
|
-
var
|
|
9309
|
+
var import_fs6 = require("fs");
|
|
9310
|
+
var import_zlib2 = require("zlib");
|
|
7199
9311
|
|
|
7200
9312
|
// libs/js/rdf-tools/src/lib/nq-to-nt.ts
|
|
7201
9313
|
var import_n37 = require("n3");
|
|
@@ -7242,8 +9354,8 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7242
9354
|
console.info(`Input: ${input} (${isGzipped ? "gzipped" : "plain"})`);
|
|
7243
9355
|
if (verbose)
|
|
7244
9356
|
console.info(`Output: ${output}`);
|
|
7245
|
-
const fileStream = (0,
|
|
7246
|
-
const inputStream = isGzipped ? fileStream.pipe((0,
|
|
9357
|
+
const fileStream = (0, import_fs6.createReadStream)(input);
|
|
9358
|
+
const inputStream = isGzipped ? fileStream.pipe((0, import_zlib2.createGunzip)()) : fileStream;
|
|
7247
9359
|
let removed = 0;
|
|
7248
9360
|
const cleanStream = removeRDFStar(inputStream, (count) => {
|
|
7249
9361
|
removed = count;
|
|
@@ -7251,9 +9363,9 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7251
9363
|
console.info(`Removed RDF-star triples so far: ${count}`);
|
|
7252
9364
|
}
|
|
7253
9365
|
});
|
|
7254
|
-
const writeStream = (0,
|
|
9366
|
+
const writeStream = (0, import_fs6.createWriteStream)(output);
|
|
7255
9367
|
if (isGzipped) {
|
|
7256
|
-
const gzip = (0,
|
|
9368
|
+
const gzip = (0, import_zlib2.createGzip)();
|
|
7257
9369
|
gzip.pipe(writeStream);
|
|
7258
9370
|
for await (const chunk of cleanStream) {
|
|
7259
9371
|
gzip.write(chunk);
|
|
@@ -7278,7 +9390,7 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7278
9390
|
}
|
|
7279
9391
|
|
|
7280
9392
|
// apps/desktop/cue-cli/src/cue-cli-util-rdf-compare.ts
|
|
7281
|
-
var
|
|
9393
|
+
var import_fs7 = require("fs");
|
|
7282
9394
|
|
|
7283
9395
|
// libs/js/rdf-compare/src/lib/js-rdf-compare.ts
|
|
7284
9396
|
var import_n39 = require("n3");
|
|
@@ -7347,8 +9459,8 @@ async function utilRdfCompareHandler(options) {
|
|
|
7347
9459
|
console.info(`File 1: ${file1}`);
|
|
7348
9460
|
if (verbose)
|
|
7349
9461
|
console.info(`File 2: ${file2}`);
|
|
7350
|
-
const content1 = (0,
|
|
7351
|
-
const content2 = (0,
|
|
9462
|
+
const content1 = (0, import_fs7.readFileSync)(file1, "utf8");
|
|
9463
|
+
const content2 = (0, import_fs7.readFileSync)(file2, "utf8");
|
|
7352
9464
|
const result = await compareTTL(content1, content2);
|
|
7353
9465
|
console.info(`File 1 triple count: ${result.file1TripleCount}`);
|
|
7354
9466
|
console.info(`File 2 triple count: ${result.file2TripleCount}`);
|
|
@@ -7376,10 +9488,10 @@ Triples only in file 2 (${result.triplesOnlyInFile2.size}):`);
|
|
|
7376
9488
|
// apps/desktop/cue-cli/src/main.ts
|
|
7377
9489
|
var packageJson;
|
|
7378
9490
|
try {
|
|
7379
|
-
packageJson = JSON.parse((0,
|
|
9491
|
+
packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "package.json"), "utf8"));
|
|
7380
9492
|
} catch {
|
|
7381
9493
|
try {
|
|
7382
|
-
packageJson = JSON.parse((0,
|
|
9494
|
+
packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "../package.json"), "utf8"));
|
|
7383
9495
|
} catch {
|
|
7384
9496
|
packageJson = { version: "0.0.0" };
|
|
7385
9497
|
console.warn("Could not find package.json, using fallback version");
|
|
@@ -7387,7 +9499,7 @@ try {
|
|
|
7387
9499
|
}
|
|
7388
9500
|
var program = new import_commander.Command();
|
|
7389
9501
|
program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
|
|
7390
|
-
program.command("sync").description("Sync files to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", 'Include zipped content (will be unzipped to path "<zip_path>_unzipped". Max uncompressed size: 500 MB, max recursion depth: 3)', false).action(syncHandler);
|
|
9502
|
+
program.command("sync").description("Sync files to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", 'Include zipped content (will be unzipped to path "<zip_path>_unzipped". Max uncompressed size: 500 MB, max recursion depth: 3)', false).option("--legacy", "Write RDF as BLOBs to the processed bucket instead of patching the graph directly", false).action(syncHandler);
|
|
7391
9503
|
program.command("dump").description("Dump Cue Knowledge Graph data to file\n Examples:\n $ cue-cli dump -s <space_id> -l -v\n $ cue-cli dump -s <space_id> -j -v").requiredOption("-s, --space <id>", "Specify the space ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-q, --query", "Uses a construct query to get the dump rather than using the /data endpoint", false).option("-j, --jelly", "Downloads a Jelly file rather than the standard Gzipped NQuads format", false).option("-l, --load", "Loads the dumped file into a local triplestore (requires emulators)", false).action(dumpHandler);
|
|
7392
9504
|
program.command("compare").description("Compares folder content to files already updated to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", "Include zipped content (will temporarily unzip files with same logic as when syncing and delete them again after the comparison)", false).action(compareHandler);
|
|
7393
9505
|
program.command("dump-processed").description("Dump processed files to local folder").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --processor <id>", "Id of the processor to dump processed files from (required) [eg. writers-blob, processors-cad-files]").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).action(dumpProcessedHandler);
|