@qaecy/cue-cli 0.0.34 → 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 +2958 -831
- 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,16 +308,92 @@ 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");
|
|
101
|
-
var EMULATOR_API_GATEWAY_PORT = process.env.CUE_EMULATOR_API_GATEWAY_PORT ?? "
|
|
381
|
+
var EMULATOR_API_GATEWAY_PORT = process.env.CUE_EMULATOR_API_GATEWAY_PORT ?? "18093";
|
|
102
382
|
var EMULATOR_API_GATEWAY_BASE = `http://localhost:${EMULATOR_API_GATEWAY_PORT}`;
|
|
103
383
|
var TOKEN_ENDPOINT_EMULATOR = `${EMULATOR_API_GATEWAY_BASE}/token`;
|
|
384
|
+
var EMULATOR_AUTH_PORT = process.env.CUE_EMULATOR_AUTH_PORT ?? "9099";
|
|
385
|
+
var EMULATOR_AUTH_HOST = `localhost:${EMULATOR_AUTH_PORT}`;
|
|
386
|
+
var EMULATOR_STORAGE_PORT = parseInt(process.env.CUE_EMULATOR_STORAGE_PORT ?? "9199", 10);
|
|
387
|
+
var EMULATOR_FIRESTORE_PORT = parseInt(process.env.CUE_EMULATOR_FIRESTORE_PORT ?? "8080", 10);
|
|
388
|
+
var getEmulatorEndpoints = () => ({
|
|
389
|
+
gatewayUrl: EMULATOR_API_GATEWAY_BASE,
|
|
390
|
+
tokenUrl: TOKEN_ENDPOINT_EMULATOR,
|
|
391
|
+
authEmulatorUrl: `http://${EMULATOR_AUTH_HOST}`,
|
|
392
|
+
storageEmulatorHost: "localhost",
|
|
393
|
+
storageEmulatorPort: EMULATOR_STORAGE_PORT,
|
|
394
|
+
firestoreEmulatorHost: "localhost",
|
|
395
|
+
firestoreEmulatorPort: EMULATOR_FIRESTORE_PORT
|
|
396
|
+
});
|
|
104
397
|
var EMULATOR_QLEVER_PORT = process.env.CUE_QLEVER_PORT ?? "8102";
|
|
105
398
|
var QLEVER_QUERY_ENDPOINT_EMULATOR = process.env.CUE_QLEVER_ENDPOINT ? `${process.env.CUE_QLEVER_ENDPOINT}/query` : `http://localhost:${EMULATOR_QLEVER_PORT}/query`;
|
|
106
399
|
var QLEVER_UPDATE_ENDPOINT_EMULATOR = process.env.CUE_QLEVER_ENDPOINT ? `${process.env.CUE_QLEVER_ENDPOINT}/update` : `http://localhost:${EMULATOR_QLEVER_PORT}/update`;
|
|
@@ -224,61 +517,761 @@ var COLLECTION_USER_TERMS_ACCEPT = "userTermsAcceptance";
|
|
|
224
517
|
var COLLECTION_TIERS = "tiers";
|
|
225
518
|
|
|
226
519
|
// libs/js/firebase/src/lib/firebase.ts
|
|
227
|
-
var
|
|
228
|
-
var
|
|
520
|
+
var import_storage2 = require("firebase/storage");
|
|
521
|
+
var import_firestore2 = require("firebase/firestore");
|
|
229
522
|
var import_auth = require("firebase/auth");
|
|
230
523
|
var import_app = require("firebase/app");
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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;
|
|
246
545
|
}
|
|
247
|
-
return _CueFirebase._instance;
|
|
248
546
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
_functionInviteUserToProject;
|
|
254
|
-
_functionRemoveUserFromProject;
|
|
255
|
-
_storageProcessed;
|
|
256
|
-
_storageRaw;
|
|
257
|
-
_storageLogs;
|
|
258
|
-
_storageChatSessions;
|
|
259
|
-
_storagePublic;
|
|
260
|
-
_storagePersistence;
|
|
261
|
-
_collectionChatSessions;
|
|
262
|
-
_collectionOrganizations;
|
|
263
|
-
_collectionProjects;
|
|
264
|
-
_collectionRDFWriting;
|
|
265
|
-
_collectionAPIKeys;
|
|
266
|
-
_collectionUsers;
|
|
267
|
-
_collectionUserTermsAcceptance;
|
|
268
|
-
_collectionTiers;
|
|
269
|
-
_auth;
|
|
270
|
-
_app;
|
|
271
|
-
get functionAcceptTerms() {
|
|
272
|
-
return this._functionAcceptTerms;
|
|
547
|
+
async ping() {
|
|
548
|
+
const query4 = "ASK { }";
|
|
549
|
+
const res = await this.query(query4);
|
|
550
|
+
return res.boolean;
|
|
273
551
|
}
|
|
274
|
-
|
|
275
|
-
|
|
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();
|
|
276
574
|
}
|
|
277
|
-
|
|
278
|
-
|
|
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();
|
|
279
590
|
}
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
};
|
|
613
|
+
}
|
|
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;
|
|
282
1275
|
}
|
|
283
1276
|
get functionChangeUserRoleOnProject() {
|
|
284
1277
|
return this._functionChangeUserRoleOnProject;
|
|
@@ -334,6 +1327,14 @@ var CueFirebase = class _CueFirebase {
|
|
|
334
1327
|
get auth() {
|
|
335
1328
|
return this._auth;
|
|
336
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
|
+
}
|
|
337
1338
|
get emulatorMode() {
|
|
338
1339
|
return this._emulator;
|
|
339
1340
|
}
|
|
@@ -370,31 +1371,40 @@ var CueFirebase = class _CueFirebase {
|
|
|
370
1371
|
functions,
|
|
371
1372
|
FUNCTION_REMOVE_USER_FROM_PROJECT_PATH
|
|
372
1373
|
);
|
|
373
|
-
this._storageProcessed = (0,
|
|
374
|
-
this._storageRaw = (0,
|
|
375
|
-
this._storageChatSessions = (0,
|
|
376
|
-
this._storageLogs = (0,
|
|
377
|
-
this._storagePublic = (0,
|
|
378
|
-
this._storagePersistence = (0,
|
|
379
|
-
this._collectionChatSessions = (0,
|
|
380
|
-
(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),
|
|
381
1382
|
COLLECTION_CHAT_SESSIONS
|
|
382
1383
|
);
|
|
383
|
-
this._collectionOrganizations = (0,
|
|
384
|
-
(0,
|
|
1384
|
+
this._collectionOrganizations = (0, import_firestore2.collection)(
|
|
1385
|
+
(0, import_firestore2.getFirestore)(app),
|
|
385
1386
|
COLLECTION_ORGANIZATIONS
|
|
386
1387
|
);
|
|
387
|
-
this._collectionProjects = (0,
|
|
388
|
-
this._collectionRDFWriting = (0,
|
|
389
|
-
this._collectionAPIKeys = (0,
|
|
390
|
-
this._collectionUsers = (0,
|
|
391
|
-
this._collectionUserTermsAcceptance = (0,
|
|
392
|
-
(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),
|
|
393
1394
|
COLLECTION_USER_TERMS_ACCEPT
|
|
394
1395
|
);
|
|
395
|
-
this._collectionTiers = (0,
|
|
1396
|
+
this._collectionTiers = (0, import_firestore2.collection)((0, import_firestore2.getFirestore)(app), COLLECTION_TIERS);
|
|
396
1397
|
this._auth = auth === void 0 ? (0, import_auth.getAuth)(app) : auth;
|
|
397
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
|
+
});
|
|
398
1408
|
this._emulator = config.useEmulator || false;
|
|
399
1409
|
if (config.useEmulator) {
|
|
400
1410
|
this.attachEmulators();
|
|
@@ -439,14 +1449,14 @@ var CueFirebase = class _CueFirebase {
|
|
|
439
1449
|
}
|
|
440
1450
|
const functions = (0, import_functions.getFunctions)(this._app, GCP_REGION);
|
|
441
1451
|
(0, import_auth.connectAuthEmulator)(this._auth, authEmulatorUrl);
|
|
442
|
-
(0,
|
|
1452
|
+
(0, import_firestore2.connectFirestoreEmulator)((0, import_firestore2.getFirestore)(this._app), firestoreHost, firestorePort);
|
|
443
1453
|
(0, import_functions.connectFunctionsEmulator)(functions, "localhost", 5001);
|
|
444
|
-
(0,
|
|
445
|
-
(0,
|
|
446
|
-
(0,
|
|
447
|
-
(0,
|
|
448
|
-
(0,
|
|
449
|
-
(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);
|
|
450
1460
|
if (!this._muted)
|
|
451
1461
|
console.info("Firebase emulators attached");
|
|
452
1462
|
}
|
|
@@ -456,7 +1466,7 @@ var CueFirebase = class _CueFirebase {
|
|
|
456
1466
|
var import_uuid = require("uuid");
|
|
457
1467
|
|
|
458
1468
|
// libs/js/sync-tools/src/lib/list-remote-files.ts
|
|
459
|
-
var
|
|
1469
|
+
var import_storage3 = require("firebase/storage");
|
|
460
1470
|
|
|
461
1471
|
// libs/js/prefixes/src/lib/qaecy-prefixes.ts
|
|
462
1472
|
var qaecyPrefixes = {
|
|
@@ -3717,11 +4727,11 @@ async function listRemoteFiles(spaceId, providerId, queryHandler2, verbose = fal
|
|
|
3717
4727
|
const storage = firebase.storageRaw;
|
|
3718
4728
|
if (!storage)
|
|
3719
4729
|
throw new Error("Firebase storage is not initialized");
|
|
3720
|
-
const listRef = (0,
|
|
4730
|
+
const listRef = (0, import_storage3.ref)(storage, spaceId);
|
|
3721
4731
|
if (verbose)
|
|
3722
4732
|
console.info(`Listing files in raw space: ${spaceId}`);
|
|
3723
4733
|
const [blobFiles, locationDataMap] = await Promise.all([
|
|
3724
|
-
(0,
|
|
4734
|
+
(0, import_storage3.listAll)(listRef),
|
|
3725
4735
|
getGraphFiles(providerId, queryHandler2)
|
|
3726
4736
|
]);
|
|
3727
4737
|
const files = [];
|
|
@@ -3770,134 +4780,13 @@ async function getGraphFiles(providerId, queryHandler2) {
|
|
|
3770
4780
|
}
|
|
3771
4781
|
|
|
3772
4782
|
// libs/js/sync-tools/src/lib/list-local-files.ts
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
var import_uuid2 = require("uuid");
|
|
3777
|
-
var replacements = {
|
|
3778
|
-
"\xE4": "ae",
|
|
3779
|
-
"a\u0308": "ae",
|
|
3780
|
-
"\xC4": "AE",
|
|
3781
|
-
"\xF6": "oe",
|
|
3782
|
-
"\xD6": "OE",
|
|
3783
|
-
"\xFC": "ue",
|
|
3784
|
-
"u\u0308": "ue",
|
|
3785
|
-
"\xDC": "UE",
|
|
3786
|
-
"U\u0308": "UE",
|
|
3787
|
-
"\xDF": "ss",
|
|
3788
|
-
"\xE6": "ae",
|
|
3789
|
-
"\xC6": "AE",
|
|
3790
|
-
"\xF8": "oe",
|
|
3791
|
-
"\xD8": "OE",
|
|
3792
|
-
"\xE5": "aa",
|
|
3793
|
-
"\xC5": "AA",
|
|
3794
|
-
"\xE1": "a",
|
|
3795
|
-
"\xC1": "A",
|
|
3796
|
-
"\xF0": "d",
|
|
3797
|
-
"\xD0": "D",
|
|
3798
|
-
"\xE9": "e",
|
|
3799
|
-
"\xC9": "E",
|
|
3800
|
-
"\xED": "i",
|
|
3801
|
-
"\xCD": "I",
|
|
3802
|
-
"\xF3": "o",
|
|
3803
|
-
"\xD3": "O",
|
|
3804
|
-
"\xFA": "u",
|
|
3805
|
-
"\xDA": "U",
|
|
3806
|
-
"\xFD": "y",
|
|
3807
|
-
"\xDD": "Y",
|
|
3808
|
-
"\xFE": "th",
|
|
3809
|
-
"\xDE": "Th"
|
|
3810
|
-
};
|
|
3811
|
-
function contextBasedGuid(contextString, verbose = false) {
|
|
3812
|
-
const namespace = "daca0510-72b5-48ba-9091-b918ca18136b";
|
|
3813
|
-
contextString = replaceSpecialChars(contextString, verbose);
|
|
3814
|
-
return (0, import_uuid2.v5)(contextString, namespace);
|
|
3815
|
-
}
|
|
3816
|
-
function replaceSpecialChars(str, verbose = false) {
|
|
3817
|
-
let result = str;
|
|
3818
|
-
for (const char in replacements) {
|
|
3819
|
-
result = result.replace(new RegExp(char, "g"), replacements[char]);
|
|
3820
|
-
}
|
|
3821
|
-
if (verbose && result !== str)
|
|
3822
|
-
console.info(`${str} -> ${result}`);
|
|
3823
|
-
return result;
|
|
3824
|
-
}
|
|
3825
|
-
function generateFileUUID(filepath, providerId = "") {
|
|
3826
|
-
return contextBasedGuid(`${providerId}${filepath}`);
|
|
4783
|
+
init_src();
|
|
4784
|
+
async function _path() {
|
|
4785
|
+
return import("path");
|
|
3827
4786
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
var import_spark_md5 = __toESM(require("spark-md5"));
|
|
3831
|
-
async function fromReadStream(readStream) {
|
|
3832
|
-
return new Promise((resolve2, reject) => {
|
|
3833
|
-
const spark = new import_spark_md5.default.ArrayBuffer();
|
|
3834
|
-
readStream.on("data", (chunk) => {
|
|
3835
|
-
spark.append(chunk);
|
|
3836
|
-
});
|
|
3837
|
-
readStream.on("end", () => {
|
|
3838
|
-
resolve2(spark.end());
|
|
3839
|
-
});
|
|
3840
|
-
readStream.on("error", (err) => reject(err));
|
|
3841
|
-
});
|
|
3842
|
-
}
|
|
3843
|
-
|
|
3844
|
-
// libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts
|
|
3845
|
-
var import_fs = require("fs");
|
|
3846
|
-
async function md5FromWorker(filePaths, hashWorkerPath, verbose = false, logIntervalPct = 1) {
|
|
3847
|
-
const { WorkerPool: WorkerPool2 } = await Promise.resolve().then(() => (init_worker_pool(), worker_pool_exports));
|
|
3848
|
-
const pool = new WorkerPool2(hashWorkerPath);
|
|
3849
|
-
const hashes = filePaths.map((filePath) => pool.hashFile(filePath));
|
|
3850
|
-
if (verbose) {
|
|
3851
|
-
let completed = 0;
|
|
3852
|
-
let lastPct = 0;
|
|
3853
|
-
hashes.forEach(
|
|
3854
|
-
(promise) => promise.then(() => {
|
|
3855
|
-
completed++;
|
|
3856
|
-
const pct = Math.floor(completed / filePaths.length * 100);
|
|
3857
|
-
if (pct - lastPct >= logIntervalPct) {
|
|
3858
|
-
lastPct = pct;
|
|
3859
|
-
console.info(`MD5 progress: ${completed}/${filePaths.length} (${pct}%)`);
|
|
3860
|
-
}
|
|
3861
|
-
})
|
|
3862
|
-
);
|
|
3863
|
-
}
|
|
3864
|
-
const ret = await Promise.all(hashes);
|
|
3865
|
-
await pool.close();
|
|
3866
|
-
return ret;
|
|
3867
|
-
}
|
|
3868
|
-
async function md5NoWorker(filePaths, verbose) {
|
|
3869
|
-
if (verbose)
|
|
3870
|
-
console.info(`Calculating MD5 hashes for ${filePaths.length} files...`);
|
|
3871
|
-
const concurrency = 50;
|
|
3872
|
-
const hashes = [];
|
|
3873
|
-
for (let i = 0; i < filePaths.length; i += concurrency) {
|
|
3874
|
-
const chunk = filePaths.slice(i, i + concurrency);
|
|
3875
|
-
const chunkHashes = await Promise.all(
|
|
3876
|
-
chunk.map((f) => {
|
|
3877
|
-
const stream = (0, import_fs.createReadStream)(f);
|
|
3878
|
-
return fromReadStream(stream);
|
|
3879
|
-
})
|
|
3880
|
-
);
|
|
3881
|
-
hashes.push(...chunkHashes);
|
|
3882
|
-
if (verbose) {
|
|
3883
|
-
const pct = Math.min(
|
|
3884
|
-
100,
|
|
3885
|
-
Math.round((i + chunk.length) / filePaths.length * 100)
|
|
3886
|
-
);
|
|
3887
|
-
console.info(
|
|
3888
|
-
`MD5 progress: ${pct}% (${i + chunk.length}/${filePaths.length})`
|
|
3889
|
-
);
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
3892
|
-
if (verbose)
|
|
3893
|
-
console.info(`Calculated MD5 hashes for ${filePaths.length} files.`);
|
|
3894
|
-
return hashes;
|
|
4787
|
+
async function _fs() {
|
|
4788
|
+
return import("fs/promises");
|
|
3895
4789
|
}
|
|
3896
|
-
|
|
3897
|
-
// libs/js/sync-tools/src/lib/list-local-files.ts
|
|
3898
|
-
var import_promises = require("fs/promises");
|
|
3899
|
-
var import_promises2 = require("fs/promises");
|
|
3900
|
-
var import_jszip = __toESM(require("jszip"));
|
|
3901
4790
|
var IGNORED = {
|
|
3902
4791
|
dirs: ["node_modules", ".git", ".hg", ".svn", ".DS_Store"],
|
|
3903
4792
|
suffix: [".tmp", ".part", ".crdownload"]
|
|
@@ -3931,10 +4820,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
|
|
|
3931
4820
|
console.warn(
|
|
3932
4821
|
`More than 10,000 files found in ${dir}. This may take a while...`
|
|
3933
4822
|
);
|
|
3934
|
-
const
|
|
3935
|
-
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)));
|
|
3936
4828
|
return fullPaths.map((f, i) => {
|
|
3937
|
-
const relativePath =
|
|
4829
|
+
const relativePath = relative(dir, f);
|
|
3938
4830
|
const md5 = hashes[i];
|
|
3939
4831
|
const contentUUID = contextBasedGuid(md5);
|
|
3940
4832
|
const locationUUID = generateFileUUID(relativePath, providerId);
|
|
@@ -3944,11 +4836,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
|
|
|
3944
4836
|
}
|
|
3945
4837
|
async function filesInLocalDirRecursive(dir, filterFunc = () => true, excludeDirs = true) {
|
|
3946
4838
|
const results = [];
|
|
4839
|
+
const { readdir } = await _fs();
|
|
4840
|
+
const { join: join4 } = await _path();
|
|
3947
4841
|
async function traverseDir(currentDir) {
|
|
3948
4842
|
try {
|
|
3949
|
-
const entries = await
|
|
4843
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
3950
4844
|
for (const entry of entries) {
|
|
3951
|
-
const fullPath = (
|
|
4845
|
+
const fullPath = join4(currentDir, entry.name);
|
|
3952
4846
|
if (entry.isFile()) {
|
|
3953
4847
|
if (filterFunc(entry)) {
|
|
3954
4848
|
results.push(fullPath);
|
|
@@ -3990,23 +4884,26 @@ async function unzipFile(zipPath, recursive = true, options) {
|
|
|
3990
4884
|
if (currentDepth > maxRecursionDepth) {
|
|
3991
4885
|
throw new Error(`Zip extraction aborted: exceeded max recursion depth (${maxRecursionDepth})`);
|
|
3992
4886
|
}
|
|
4887
|
+
const { mkdir: mkdir2, readFile, writeFile: writeFile2 } = await _fs();
|
|
4888
|
+
const { join: join4 } = await _path();
|
|
4889
|
+
const { default: JSZip } = await import("jszip");
|
|
3993
4890
|
const targetDir = zipPath.replace(/\.zip$/i, "") + UNZIPPED_SUFFIX;
|
|
3994
|
-
await (
|
|
3995
|
-
const data = await
|
|
3996
|
-
const zip = await
|
|
4891
|
+
await mkdir2(targetDir, { recursive: true });
|
|
4892
|
+
const data = await readFile(zipPath);
|
|
4893
|
+
const zip = await JSZip.loadAsync(data);
|
|
3997
4894
|
const extractPromises = [];
|
|
3998
4895
|
for (const [relativePath, file] of Object.entries(zip.files)) {
|
|
3999
4896
|
if (!file.dir) {
|
|
4000
4897
|
const pathParts = relativePath.split("/");
|
|
4001
|
-
const fileTargetDir = (
|
|
4002
|
-
const targetPath = (
|
|
4898
|
+
const fileTargetDir = join4(targetDir, ...pathParts.slice(0, -1));
|
|
4899
|
+
const targetPath = join4(fileTargetDir, pathParts[pathParts.length - 1]);
|
|
4003
4900
|
await ensureDir(fileTargetDir);
|
|
4004
4901
|
const promise = file.async("nodebuffer").then(async (content) => {
|
|
4005
4902
|
totalUncompressedSize.value += content.length;
|
|
4006
4903
|
if (totalUncompressedSize.value > maxUncompressedSize) {
|
|
4007
4904
|
throw new Error(`Zip extraction aborted: exceeded max uncompressed size (${maxUncompressedSize} bytes)`);
|
|
4008
4905
|
}
|
|
4009
|
-
await (
|
|
4906
|
+
await writeFile2(targetPath, content);
|
|
4010
4907
|
if (recursive && targetPath.toLowerCase().endsWith(".zip")) {
|
|
4011
4908
|
await unzipFile(targetPath, true, {
|
|
4012
4909
|
maxUncompressedSize,
|
|
@@ -4022,18 +4919,20 @@ async function unzipFile(zipPath, recursive = true, options) {
|
|
|
4022
4919
|
await Promise.all(extractPromises);
|
|
4023
4920
|
}
|
|
4024
4921
|
async function ensureDir(path) {
|
|
4025
|
-
|
|
4922
|
+
const { mkdir: mkdir2 } = await _fs();
|
|
4923
|
+
await mkdir2(path, { recursive: true });
|
|
4026
4924
|
}
|
|
4027
4925
|
async function deleteUnzipped(dir) {
|
|
4926
|
+
const { rm } = await _fs();
|
|
4028
4927
|
const paths = await filesInLocalDirRecursive(dir, (entry) => entry.name.toLowerCase().endsWith(UNZIPPED_SUFFIX), false);
|
|
4029
4928
|
for (const p of paths) {
|
|
4030
|
-
await
|
|
4929
|
+
await rm(p, { recursive: true, force: true });
|
|
4031
4930
|
}
|
|
4032
4931
|
}
|
|
4033
4932
|
|
|
4034
4933
|
// apps/desktop/cue-cli/src/helpers/query-handler.ts
|
|
4035
4934
|
var import_auth2 = require("firebase/auth");
|
|
4036
|
-
async function queryHandler(
|
|
4935
|
+
async function queryHandler(query4, spaceId, useEmulator) {
|
|
4037
4936
|
const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
|
|
4038
4937
|
const token = await (0, import_auth2.getAuth)().currentUser?.getIdToken();
|
|
4039
4938
|
const res = await fetch(endpoint, {
|
|
@@ -4044,7 +4943,7 @@ async function queryHandler(query3, spaceId, useEmulator) {
|
|
|
4044
4943
|
"Content-Type": "application/sparql-query",
|
|
4045
4944
|
Accept: "application/sparql-results+json"
|
|
4046
4945
|
},
|
|
4047
|
-
body:
|
|
4946
|
+
body: query4
|
|
4048
4947
|
});
|
|
4049
4948
|
if (!res.ok) {
|
|
4050
4949
|
console.error(
|
|
@@ -4066,13 +4965,138 @@ function fileSizePretty(size) {
|
|
|
4066
4965
|
|
|
4067
4966
|
// libs/js/cue-sdk/src/lib/cue.ts
|
|
4068
4967
|
var import_app2 = require("firebase/app");
|
|
4968
|
+
var import_storage5 = require("firebase/storage");
|
|
4069
4969
|
|
|
4070
4970
|
// libs/js/cue-sdk/src/lib/auth.ts
|
|
4071
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";
|
|
4072
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
|
|
4073
5084
|
var CueAuth = class {
|
|
4074
5085
|
_auth;
|
|
4075
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;
|
|
4076
5100
|
constructor(app, useEmulator = false, endpoints) {
|
|
4077
5101
|
this._auth = (0, import_auth3.getAuth)(app);
|
|
4078
5102
|
this._endpoints = endpoints;
|
|
@@ -4081,6 +5105,35 @@ var CueAuth = class {
|
|
|
4081
5105
|
disableWarnings: true
|
|
4082
5106
|
});
|
|
4083
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?.();
|
|
4084
5137
|
}
|
|
4085
5138
|
async signIn(provider, credentials) {
|
|
4086
5139
|
if (provider === "password") {
|
|
@@ -4097,6 +5150,36 @@ var CueAuth = class {
|
|
|
4097
5150
|
const result = await (0, import_auth3.signInWithPopup)(this._auth, firebaseProvider);
|
|
4098
5151
|
return result.user;
|
|
4099
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
|
+
}
|
|
4100
5183
|
/** Sign in with a Cue API key */
|
|
4101
5184
|
async signInWithApiKey(cueApiKey, projectId) {
|
|
4102
5185
|
const response = await fetch(this._endpoints.tokenUrl, {
|
|
@@ -4131,6 +5214,30 @@ var CueAuth = class {
|
|
|
4131
5214
|
return null;
|
|
4132
5215
|
return user.getIdToken(forceRefresh);
|
|
4133
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
|
+
}
|
|
4134
5241
|
/** Raw Firebase Auth instance, for advanced use cases */
|
|
4135
5242
|
get firebaseAuth() {
|
|
4136
5243
|
return this._auth;
|
|
@@ -4138,9 +5245,6 @@ var CueAuth = class {
|
|
|
4138
5245
|
};
|
|
4139
5246
|
|
|
4140
5247
|
// libs/js/cue-sdk/src/lib/api.ts
|
|
4141
|
-
var ENDPOINT_SEARCH = "/assistant/search";
|
|
4142
|
-
var ENDPOINT_SPARQL = "/triplestore/query";
|
|
4143
|
-
var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
|
|
4144
5248
|
var CueApi = class {
|
|
4145
5249
|
constructor(_auth, _gatewayUrl, projects, sync) {
|
|
4146
5250
|
this._auth = _auth;
|
|
@@ -4148,9 +5252,6 @@ var CueApi = class {
|
|
|
4148
5252
|
this.projects = projects;
|
|
4149
5253
|
this.sync = sync;
|
|
4150
5254
|
}
|
|
4151
|
-
async _authHeaders() {
|
|
4152
|
-
return this.getAuthHeaders();
|
|
4153
|
-
}
|
|
4154
5255
|
/**
|
|
4155
5256
|
* Returns standard authentication headers for the current user.
|
|
4156
5257
|
* Useful when calling Cue-backed services directly (e.g. the GIS proxy).
|
|
@@ -4169,18 +5270,22 @@ var CueApi = class {
|
|
|
4169
5270
|
* The user must be authenticated before calling this.
|
|
4170
5271
|
*/
|
|
4171
5272
|
async search(request) {
|
|
4172
|
-
const
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
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
|
+
);
|
|
4182
5285
|
if (!response.ok) {
|
|
4183
|
-
throw new Error(
|
|
5286
|
+
throw new Error(
|
|
5287
|
+
`Search request failed: ${response.status} ${response.statusText}`
|
|
5288
|
+
);
|
|
4184
5289
|
}
|
|
4185
5290
|
return response.json();
|
|
4186
5291
|
}
|
|
@@ -4188,48 +5293,71 @@ var CueApi = class {
|
|
|
4188
5293
|
* Execute a SPARQL query against the project's triplestore.
|
|
4189
5294
|
* The user must be authenticated before calling this.
|
|
4190
5295
|
*/
|
|
4191
|
-
async sparql(
|
|
4192
|
-
|
|
4193
|
-
const
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
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
|
+
);
|
|
4198
5315
|
if (!response.ok) {
|
|
4199
|
-
throw new Error(
|
|
5316
|
+
throw new Error(
|
|
5317
|
+
`SPARQL query failed: ${response.status} ${response.statusText}`
|
|
5318
|
+
);
|
|
4200
5319
|
}
|
|
4201
5320
|
return response.json();
|
|
4202
5321
|
}
|
|
4203
|
-
async getConsumption(projectId
|
|
4204
|
-
const
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
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
|
+
}
|
|
4210
5331
|
}
|
|
4211
|
-
|
|
5332
|
+
);
|
|
4212
5333
|
if (!response.ok) {
|
|
4213
|
-
throw new Error(
|
|
5334
|
+
throw new Error(
|
|
5335
|
+
`Failed to fetch consumption: ${response.status} ${response.statusText}`
|
|
5336
|
+
);
|
|
4214
5337
|
}
|
|
4215
5338
|
return response.json();
|
|
4216
5339
|
}
|
|
4217
5340
|
};
|
|
4218
5341
|
|
|
4219
5342
|
// libs/js/cue-sdk/src/lib/project.ts
|
|
4220
|
-
var
|
|
4221
|
-
var
|
|
5343
|
+
var import_firestore3 = require("firebase/firestore");
|
|
5344
|
+
var import_functions2 = require("firebase/functions");
|
|
4222
5345
|
var CueProjects = class {
|
|
4223
5346
|
constructor(_auth, app, useEmulator = false, endpoints) {
|
|
4224
5347
|
this._auth = _auth;
|
|
4225
|
-
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 ?? "";
|
|
4226
5351
|
if (useEmulator) {
|
|
4227
5352
|
const host = endpoints?.firestoreEmulatorHost ?? "localhost";
|
|
4228
5353
|
const port = endpoints?.firestoreEmulatorPort ?? 8080;
|
|
4229
|
-
(0,
|
|
5354
|
+
(0, import_firestore3.connectFirestoreEmulator)(this._db, host, port);
|
|
5355
|
+
(0, import_functions2.connectFunctionsEmulator)(this._functions, "localhost", 5001);
|
|
4230
5356
|
}
|
|
4231
5357
|
}
|
|
4232
5358
|
_db;
|
|
5359
|
+
_functions;
|
|
5360
|
+
_gatewayUrl;
|
|
4233
5361
|
_requireUser() {
|
|
4234
5362
|
const user = this._auth.currentUser;
|
|
4235
5363
|
if (!user)
|
|
@@ -4241,60 +5369,31 @@ var CueProjects = class {
|
|
|
4241
5369
|
* Throws if a project with the given ID already exists.
|
|
4242
5370
|
*/
|
|
4243
5371
|
async createProject(options) {
|
|
4244
|
-
const
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
throw new Error(`
|
|
4252
|
-
}
|
|
4253
|
-
|
|
4254
|
-
id: projectId,
|
|
4255
|
-
name: options.name,
|
|
4256
|
-
organizationID: options.organizationID,
|
|
4257
|
-
created: now,
|
|
4258
|
-
lastSync: null,
|
|
4259
|
-
isPublic: false,
|
|
4260
|
-
members: [userId],
|
|
4261
|
-
syncers: [userId],
|
|
4262
|
-
admins: [userId],
|
|
4263
|
-
alternativeIDs: [projectId],
|
|
4264
|
-
projectSettings
|
|
4265
|
-
};
|
|
4266
|
-
await (0, import_firestore2.setDoc)(ref5, data);
|
|
4267
|
-
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();
|
|
4268
5382
|
}
|
|
4269
5383
|
/**
|
|
4270
|
-
* List all projects where the authenticated user appears in the members
|
|
4271
|
-
*
|
|
5384
|
+
* List all projects where the authenticated user appears in the members array.
|
|
5385
|
+
* Access is gated by Firestore rules which check membership.
|
|
4272
5386
|
*/
|
|
4273
5387
|
async listProjects() {
|
|
4274
5388
|
const userId = this._requireUser();
|
|
4275
|
-
const col = (0,
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
(0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("syncers", "array-contains", userId))),
|
|
4279
|
-
(0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("admins", "array-contains", userId)))
|
|
4280
|
-
]);
|
|
4281
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4282
|
-
const results = [];
|
|
4283
|
-
for (const snap of [memberSnap, syncerSnap, adminSnap]) {
|
|
4284
|
-
for (const d of snap.docs) {
|
|
4285
|
-
const project = d.data();
|
|
4286
|
-
if (!seen.has(project.id)) {
|
|
4287
|
-
seen.add(project.id);
|
|
4288
|
-
results.push(project);
|
|
4289
|
-
}
|
|
4290
|
-
}
|
|
4291
|
-
}
|
|
4292
|
-
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());
|
|
4293
5392
|
}
|
|
4294
5393
|
/** Fetch a single project by ID. Returns null if not found. */
|
|
4295
5394
|
async getProject(projectId) {
|
|
4296
|
-
const
|
|
4297
|
-
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);
|
|
4298
5397
|
if (!snap.exists())
|
|
4299
5398
|
return null;
|
|
4300
5399
|
return snap.data();
|
|
@@ -4306,28 +5405,49 @@ var CueProjects = class {
|
|
|
4306
5405
|
async incrementUnitsConsumed(projectId, units, userId) {
|
|
4307
5406
|
if (units <= 0)
|
|
4308
5407
|
return;
|
|
4309
|
-
const
|
|
4310
|
-
await (0,
|
|
4311
|
-
unitsConsumed: (0,
|
|
4312
|
-
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)(),
|
|
4313
5412
|
lastUserId: userId
|
|
4314
5413
|
}, { merge: true });
|
|
4315
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
|
+
}
|
|
4316
5433
|
};
|
|
4317
5434
|
|
|
4318
5435
|
// libs/js/cue-sdk/src/lib/profile.ts
|
|
4319
5436
|
var import_auth4 = require("firebase/auth");
|
|
4320
|
-
var
|
|
4321
|
-
var
|
|
5437
|
+
var import_firestore4 = require("firebase/firestore");
|
|
5438
|
+
var import_functions3 = require("firebase/functions");
|
|
4322
5439
|
var CueProfile = class {
|
|
4323
5440
|
constructor(_auth, app, useEmulator, emulatorHost, emulatorPort) {
|
|
4324
5441
|
this._auth = _auth;
|
|
4325
|
-
this._db = (0,
|
|
5442
|
+
this._db = (0, import_firestore4.getFirestore)(app);
|
|
5443
|
+
this._functions = (0, import_functions3.getFunctions)(app, GCP_REGION2);
|
|
4326
5444
|
if (useEmulator) {
|
|
4327
|
-
(0,
|
|
5445
|
+
(0, import_firestore4.connectFirestoreEmulator)(this._db, emulatorHost, emulatorPort);
|
|
5446
|
+
(0, import_functions3.connectFunctionsEmulator)(this._functions, "localhost", 5001);
|
|
4328
5447
|
}
|
|
4329
5448
|
}
|
|
4330
5449
|
_db;
|
|
5450
|
+
_functions;
|
|
4331
5451
|
_apiKeyDocRef;
|
|
4332
5452
|
/** Whether the current user has an active API key. */
|
|
4333
5453
|
async hasAPIKey() {
|
|
@@ -4408,512 +5528,1197 @@ var CueProfile = class {
|
|
|
4408
5528
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4409
5529
|
const randomKey = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(150)), (b) => alphabet[b & 63]).join("");
|
|
4410
5530
|
const data = { key: `cue-${randomKey}`, uid, expiration };
|
|
4411
|
-
const col = (0,
|
|
4412
|
-
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);
|
|
4413
5533
|
return data;
|
|
4414
5534
|
}
|
|
4415
5535
|
/** Revokes the current user's API key. */
|
|
4416
5536
|
async revokeAPIKey() {
|
|
4417
5537
|
if (!this._apiKeyDocRef)
|
|
4418
5538
|
throw new Error("No API key document reference");
|
|
4419
|
-
await (0,
|
|
5539
|
+
await (0, import_firestore4.deleteDoc)(this._apiKeyDocRef);
|
|
4420
5540
|
this._apiKeyDocRef = void 0;
|
|
4421
5541
|
}
|
|
4422
5542
|
/** Fetches the current user's existing API key. */
|
|
4423
5543
|
async requestAPIKey() {
|
|
4424
5544
|
if (!this._apiKeyDocRef)
|
|
4425
5545
|
throw new Error("No API key document reference");
|
|
4426
|
-
const
|
|
4427
|
-
return { key:
|
|
5546
|
+
const doc3 = (await (0, import_firestore4.getDoc)(this._apiKeyDocRef)).data();
|
|
5547
|
+
return { key: doc3.key, expiration: doc3.expiration };
|
|
4428
5548
|
}
|
|
4429
5549
|
async _checkIfUserHasAPIKey(uid) {
|
|
4430
|
-
const col = (0,
|
|
4431
|
-
const q = (0,
|
|
4432
|
-
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);
|
|
4433
5553
|
if (!snapshot.empty)
|
|
4434
5554
|
this._apiKeyDocRef = snapshot.docs[0].ref;
|
|
4435
5555
|
return !snapshot.empty;
|
|
4436
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
|
+
}
|
|
4437
5567
|
_requireUser() {
|
|
4438
5568
|
const user = this._auth.currentUser;
|
|
4439
5569
|
if (!user)
|
|
4440
5570
|
throw new Error("Not authenticated");
|
|
4441
5571
|
return user;
|
|
4442
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
|
+
}
|
|
4443
5587
|
};
|
|
4444
5588
|
|
|
4445
|
-
// libs/js/cue-sdk/src/lib/
|
|
4446
|
-
var
|
|
4447
|
-
var
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
authEmulatorUrl: "http://localhost:9099",
|
|
4462
|
-
storageEmulatorHost: "localhost",
|
|
4463
|
-
storageEmulatorPort: 9199,
|
|
4464
|
-
firestoreEmulatorHost: "localhost",
|
|
4465
|
-
firestoreEmulatorPort: 8080
|
|
4466
|
-
}
|
|
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"
|
|
4467
5605
|
};
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
this.
|
|
4488
|
-
|
|
4489
|
-
this.
|
|
4490
|
-
|
|
4491
|
-
this.
|
|
4492
|
-
this._app,
|
|
4493
|
-
this._isEmulator,
|
|
4494
|
-
this._endpoints.firestoreEmulatorHost,
|
|
4495
|
-
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()
|
|
4496
5630
|
);
|
|
4497
5631
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
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
|
+
}
|
|
4501
5653
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
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;
|
|
4505
5670
|
}
|
|
4506
5671
|
};
|
|
4507
5672
|
|
|
4508
|
-
// libs/js/cue-sdk/src/lib/
|
|
5673
|
+
// libs/js/cue-sdk/src/lib/cache.ts
|
|
4509
5674
|
var import_storage4 = require("firebase/storage");
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
queryEndpoint;
|
|
4514
|
-
updateEndpoint;
|
|
4515
|
-
baseHeaders;
|
|
4516
|
-
static RELEVANT_HEADER_KEYS = [
|
|
4517
|
-
"authorization",
|
|
4518
|
-
"Authorization",
|
|
4519
|
-
"x-project-id"
|
|
4520
|
-
];
|
|
4521
|
-
constructor(graphOptions) {
|
|
4522
|
-
this.queryEndpoint = graphOptions.queryEndpoint;
|
|
4523
|
-
this.updateEndpoint = graphOptions.updateEndpoint;
|
|
4524
|
-
this.baseHeaders = Object.fromEntries(
|
|
4525
|
-
Object.entries(graphOptions.originalHeaders || {}).filter(
|
|
4526
|
-
([key]) => _Fuseki.RELEVANT_HEADER_KEYS.includes(key)
|
|
4527
|
-
)
|
|
4528
|
-
);
|
|
4529
|
-
if (graphOptions.authHeader !== void 0) {
|
|
4530
|
-
this.baseHeaders["Authorization"] = graphOptions.authHeader;
|
|
4531
|
-
}
|
|
5675
|
+
var CueCache = class {
|
|
5676
|
+
constructor(_storage) {
|
|
5677
|
+
this._storage = _storage;
|
|
4532
5678
|
}
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
return res.boolean;
|
|
5679
|
+
// ── Query cache ────────────────────────────────────────────────────────────
|
|
5680
|
+
async getQueryCache(projectId, id) {
|
|
5681
|
+
return this._get(`portal/projects/${projectId}/queries`, id);
|
|
4537
5682
|
}
|
|
4538
|
-
async
|
|
4539
|
-
|
|
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) {
|
|
4540
5706
|
try {
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
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));
|
|
4550
5728
|
} catch (err) {
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
5729
|
+
if (_isNotFound(err))
|
|
5730
|
+
return void 0;
|
|
5731
|
+
throw err;
|
|
4554
5732
|
}
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
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();
|
|
4558
5746
|
}
|
|
4559
|
-
|
|
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
|
+
);
|
|
4560
5759
|
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
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 };
|
|
4568
5839
|
},
|
|
4569
|
-
|
|
4570
|
-
|
|
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
|
+
};
|
|
4571
5878
|
});
|
|
4572
|
-
if (accept === "application/ld+json") {
|
|
4573
|
-
return await res.json();
|
|
4574
|
-
}
|
|
4575
|
-
return await res.text();
|
|
4576
5879
|
}
|
|
4577
|
-
async
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
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
|
+
};
|
|
4585
5906
|
});
|
|
4586
|
-
if (!res.ok) {
|
|
4587
|
-
const body = await res.text();
|
|
4588
|
-
throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
|
|
4589
|
-
}
|
|
4590
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
4591
|
-
if (contentType.includes("application/json")) {
|
|
4592
|
-
return await res.json();
|
|
4593
|
-
}
|
|
4594
|
-
return {
|
|
4595
|
-
ok: res.ok,
|
|
4596
|
-
status: res.status,
|
|
4597
|
-
message: await res.text()
|
|
4598
|
-
};
|
|
4599
5907
|
}
|
|
4600
5908
|
};
|
|
4601
5909
|
|
|
4602
|
-
// libs/js/
|
|
4603
|
-
var
|
|
4604
|
-
var
|
|
4605
|
-
var
|
|
4606
|
-
constructor(
|
|
4607
|
-
|
|
4608
|
-
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
|
+
);
|
|
4609
6017
|
}
|
|
4610
|
-
};
|
|
4611
|
-
var QLever = class _QLever {
|
|
4612
|
-
queryEndpoint;
|
|
4613
|
-
updateEndpoint;
|
|
4614
|
-
dataEndpoint;
|
|
4615
|
-
baseHeaders;
|
|
4616
|
-
static RELEVANT_HEADER_KEYS = [
|
|
4617
|
-
"authorization",
|
|
4618
|
-
"Authorization",
|
|
4619
|
-
"x-project-id",
|
|
4620
|
-
"x-user-roles"
|
|
4621
|
-
// add more if needed
|
|
4622
|
-
];
|
|
4623
|
-
/** Max retries on 423 Locked (rebuild in progress). */
|
|
4624
|
-
static LOCKED_MAX_RETRIES = parseInt(process.env["QLEVER_LOCKED_MAX_RETRIES"] ?? "10", 10);
|
|
4625
|
-
/** Base delay (ms) for exponential backoff on 423. */
|
|
4626
|
-
static LOCKED_BASE_DELAY_MS = parseInt(process.env["QLEVER_LOCKED_BASE_DELAY_MS"] ?? "2000", 10);
|
|
4627
6018
|
/**
|
|
4628
|
-
*
|
|
4629
|
-
*
|
|
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.
|
|
4630
6024
|
*/
|
|
4631
|
-
|
|
4632
|
-
const
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
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)
|
|
4648
6045
|
}
|
|
4649
|
-
throw lastError;
|
|
4650
6046
|
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
)
|
|
4659
|
-
|
|
4660
|
-
|
|
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);
|
|
4661
6081
|
}
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
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;
|
|
4666
6103
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
} catch (err) {
|
|
4680
|
-
throw new Error(
|
|
4681
|
-
`QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
|
|
4682
|
-
);
|
|
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]);
|
|
4683
6116
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
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
|
+
};
|
|
4687
6188
|
}
|
|
4688
|
-
return
|
|
6189
|
+
return result;
|
|
4689
6190
|
}
|
|
4690
|
-
async
|
|
4691
|
-
const
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
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
|
+
};
|
|
4699
6221
|
});
|
|
4700
|
-
|
|
6222
|
+
this._entityDetails.set(detailsUpdate);
|
|
6223
|
+
return result;
|
|
4701
6224
|
}
|
|
4702
|
-
async
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
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
|
|
4711
6249
|
});
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
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);
|
|
4717
6303
|
}
|
|
4718
|
-
|
|
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);
|
|
4719
6336
|
});
|
|
6337
|
+
this._osmWKTMap.set(update);
|
|
4720
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 ──────────────────────────────────────────────────────────────
|
|
4721
6367
|
/**
|
|
4722
|
-
*
|
|
4723
|
-
*
|
|
4724
|
-
* 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.
|
|
4725
6370
|
*/
|
|
4726
|
-
|
|
4727
|
-
|
|
6371
|
+
reset() {
|
|
6372
|
+
this._documentInfoMap.set({});
|
|
6373
|
+
this._projectDocumentsData.set({
|
|
6374
|
+
duplicateCount: 0,
|
|
6375
|
+
documentsBySuffix: {},
|
|
6376
|
+
documentsByContentCategory: {}
|
|
6377
|
+
});
|
|
4728
6378
|
}
|
|
4729
6379
|
/**
|
|
4730
|
-
*
|
|
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.
|
|
4731
6383
|
*/
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
const body = new Uint8Array((0, import_zlib.gzipSync)(Buffer.from(nquads, "utf-8")));
|
|
4738
|
-
await _QLever._retryOnLocked(async () => {
|
|
4739
|
-
const res = await fetch(baseUrl, {
|
|
4740
|
-
method: "POST",
|
|
4741
|
-
headers: {
|
|
4742
|
-
...this.baseHeaders,
|
|
4743
|
-
"Content-Type": "application/n-quads",
|
|
4744
|
-
"Content-Encoding": "gzip"
|
|
4745
|
-
},
|
|
4746
|
-
body
|
|
4747
|
-
});
|
|
4748
|
-
if (!res.ok) {
|
|
4749
|
-
const text = await res.text();
|
|
4750
|
-
if (res.status === 423)
|
|
4751
|
-
throw new QLeverLockedError(text);
|
|
4752
|
-
throw new Error(`QLever data POST failed (HTTP ${res.status}): ${text}`);
|
|
4753
|
-
}
|
|
4754
|
-
});
|
|
6384
|
+
setLanguage(lang) {
|
|
6385
|
+
if (this._language === lang)
|
|
6386
|
+
return;
|
|
6387
|
+
this._language = lang;
|
|
6388
|
+
this._documentInfoMap.set({});
|
|
4755
6389
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
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: {}
|
|
4761
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
|
+
);
|
|
4762
6418
|
}
|
|
4763
|
-
|
|
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 }
|
|
4764
6445
|
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
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 }
|
|
4778
6497
|
}
|
|
4779
6498
|
}
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
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;
|
|
4783
6515
|
}
|
|
4784
|
-
|
|
4785
|
-
return this.
|
|
6516
|
+
async _fetchDocumentsByContentCategory() {
|
|
6517
|
+
return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
|
|
4786
6518
|
}
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
accept
|
|
4799
|
-
);
|
|
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 }
|
|
4800
6530
|
}
|
|
4801
|
-
return this._db.subset(queryString, accept);
|
|
4802
6531
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
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;
|
|
4805
6548
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
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);
|
|
4809
6626
|
}
|
|
4810
6627
|
/**
|
|
4811
|
-
*
|
|
4812
|
-
*
|
|
4813
|
-
* 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.
|
|
4814
6630
|
*/
|
|
4815
|
-
|
|
4816
|
-
if (this.
|
|
4817
|
-
return
|
|
4818
|
-
|
|
4819
|
-
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);
|
|
4820
6635
|
}
|
|
4821
6636
|
/**
|
|
4822
|
-
*
|
|
4823
|
-
*
|
|
4824
|
-
* 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`.
|
|
4825
6639
|
*/
|
|
4826
|
-
|
|
4827
|
-
if (this.
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
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);
|
|
4831
6644
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
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);
|
|
4839
6657
|
}
|
|
6658
|
+
// ── Document methods ───────────────────────────────────────────────────────
|
|
4840
6659
|
/**
|
|
4841
|
-
*
|
|
6660
|
+
* Lazily batch-fetch document info for the given UUIDs.
|
|
6661
|
+
* Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
|
|
4842
6662
|
*/
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
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);
|
|
4863
6683
|
}
|
|
4864
|
-
throw lastError;
|
|
4865
6684
|
}
|
|
6685
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
4866
6686
|
/**
|
|
4867
|
-
*
|
|
4868
|
-
*
|
|
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.
|
|
4869
6690
|
*/
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
await (0, import_storage3.uploadBytes)(fileRef, data, { customMetadata: metadata });
|
|
4876
|
-
return true;
|
|
6691
|
+
setLanguage(lang) {
|
|
6692
|
+
if (this._destroyed)
|
|
6693
|
+
return;
|
|
6694
|
+
this.schema.setLanguage(lang);
|
|
6695
|
+
this.documents.setLanguage(lang);
|
|
4877
6696
|
}
|
|
4878
6697
|
/**
|
|
4879
|
-
*
|
|
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.
|
|
4880
6701
|
*/
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
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));
|
|
4885
6709
|
}
|
|
4886
6710
|
/**
|
|
4887
|
-
*
|
|
6711
|
+
* Tear down this view instance. Clears all reactive state and blocks further
|
|
6712
|
+
* updates. Call from the Angular adapter's `ngOnDestroy` or equivalent.
|
|
4888
6713
|
*/
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
4893
|
-
try {
|
|
4894
|
-
const [url, metadata] = await Promise.all([
|
|
4895
|
-
(0, import_storage3.getDownloadURL)(fileRef),
|
|
4896
|
-
(0, import_storage3.getMetadata)(fileRef)
|
|
4897
|
-
]);
|
|
4898
|
-
const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
|
|
4899
|
-
const res = await fetch(cacheBustedUrl, { signal: controller.signal });
|
|
4900
|
-
if (!res.ok)
|
|
4901
|
-
throw new Error(`HTTP ${res.status}`);
|
|
4902
|
-
return res.text();
|
|
4903
|
-
} catch (err) {
|
|
4904
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
4905
|
-
throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
|
|
4906
|
-
} finally {
|
|
4907
|
-
clearTimeout(timeout);
|
|
4908
|
-
}
|
|
6714
|
+
destroy() {
|
|
6715
|
+
this._destroyed = true;
|
|
6716
|
+
this._searchResults.set(void 0);
|
|
4909
6717
|
}
|
|
4910
6718
|
};
|
|
4911
6719
|
|
|
4912
|
-
// libs/js/
|
|
4913
|
-
|
|
4914
|
-
var import_path3 = require("path");
|
|
4915
|
-
var import_url = require("url");
|
|
4916
|
-
var import_os = require("os");
|
|
6720
|
+
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
6721
|
+
init_src();
|
|
4917
6722
|
|
|
4918
6723
|
// libs/js/models/src/lib/file-extensions.ts
|
|
4919
6724
|
var fileExtensionsInfo = {
|
|
@@ -5815,9 +7620,11 @@ var import_uuid5 = require("uuid");
|
|
|
5815
7620
|
|
|
5816
7621
|
// libs/js/rdf-document-writers/src/lib/alternative-representation.ts
|
|
5817
7622
|
var import_n33 = require("n3");
|
|
7623
|
+
init_src();
|
|
5818
7624
|
|
|
5819
7625
|
// libs/js/rdf-document-writers/src/lib/file-location.ts
|
|
5820
7626
|
var import_n32 = require("n3");
|
|
7627
|
+
init_src();
|
|
5821
7628
|
var { namedNode, literal } = import_n32.DataFactory;
|
|
5822
7629
|
var a = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5823
7630
|
var prefixes = {
|
|
@@ -5840,6 +7647,7 @@ var { namedNode: namedNode3, literal: literal2 } = import_n34.DataFactory;
|
|
|
5840
7647
|
var a2 = namedNode3("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5841
7648
|
|
|
5842
7649
|
// libs/js/rdf-document-writers/src/lib/document-file.ts
|
|
7650
|
+
init_src();
|
|
5843
7651
|
var import_n35 = require("n3");
|
|
5844
7652
|
|
|
5845
7653
|
// libs/js/rdf-document-writers/src/lib/file-suffix.ts
|
|
@@ -5858,6 +7666,7 @@ var a3 = namedNode4("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
|
5858
7666
|
// libs/js/rdf-document-writers/src/lib/process-logs.ts
|
|
5859
7667
|
var import_n36 = require("n3");
|
|
5860
7668
|
var import_uuid6 = require("uuid");
|
|
7669
|
+
init_src();
|
|
5861
7670
|
var { namedNode: namedNode5, literal: literal4 } = import_n36.DataFactory;
|
|
5862
7671
|
var a4 = namedNode5("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
5863
7672
|
var prefixes3 = {
|
|
@@ -5898,29 +7707,59 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
|
|
|
5898
7707
|
}
|
|
5899
7708
|
|
|
5900
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
|
+
}
|
|
5901
7719
|
var _scanFn = null;
|
|
5902
7720
|
var _wasmInitPromise = null;
|
|
7721
|
+
var _browserWasmBaseUrl = null;
|
|
5903
7722
|
async function _initWasm() {
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
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
|
+
}
|
|
5913
7755
|
}
|
|
5914
7756
|
var DEFAULT_GRAPH_TYPE = "fuseki";
|
|
5915
|
-
var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
|
|
5916
|
-
var ENDPOINT_QLEVER_QUERY = "/sparql/query";
|
|
5917
|
-
var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
|
|
5918
|
-
var ENDPOINT_QLEVER_UPDATE = "/sparql/update";
|
|
5919
|
-
var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
|
|
5920
7757
|
var FSS_BATCH_CHUNK_SIZE = 1e3;
|
|
5921
7758
|
var PENDING_LS_PREFIX = "cue:pending:";
|
|
5922
|
-
function _pendingFilePath(spaceId) {
|
|
5923
|
-
|
|
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`);
|
|
5924
7763
|
}
|
|
5925
7764
|
async function _loadPending(spaceId) {
|
|
5926
7765
|
if (typeof window !== "undefined") {
|
|
@@ -5928,7 +7767,7 @@ async function _loadPending(spaceId) {
|
|
|
5928
7767
|
return raw ? JSON.parse(raw) : null;
|
|
5929
7768
|
}
|
|
5930
7769
|
try {
|
|
5931
|
-
const raw = await (
|
|
7770
|
+
const raw = await (await import("fs/promises")).readFile(await _pendingFilePath(spaceId), "utf-8");
|
|
5932
7771
|
return JSON.parse(raw);
|
|
5933
7772
|
} catch {
|
|
5934
7773
|
return null;
|
|
@@ -5940,7 +7779,7 @@ async function _savePending(batch) {
|
|
|
5940
7779
|
window.localStorage.setItem(`${PENDING_LS_PREFIX}${batch.spaceId}`, data);
|
|
5941
7780
|
return;
|
|
5942
7781
|
}
|
|
5943
|
-
await (
|
|
7782
|
+
await (await import("fs/promises")).writeFile(await _pendingFilePath(batch.spaceId), data, "utf-8");
|
|
5944
7783
|
}
|
|
5945
7784
|
async function _clearPending(spaceId) {
|
|
5946
7785
|
if (typeof window !== "undefined") {
|
|
@@ -5948,7 +7787,7 @@ async function _clearPending(spaceId) {
|
|
|
5948
7787
|
return;
|
|
5949
7788
|
}
|
|
5950
7789
|
try {
|
|
5951
|
-
await (
|
|
7790
|
+
await (await import("fs/promises")).unlink(await _pendingFilePath(spaceId));
|
|
5952
7791
|
} catch {
|
|
5953
7792
|
}
|
|
5954
7793
|
}
|
|
@@ -5964,18 +7803,30 @@ var CueSyncApi = class {
|
|
|
5964
7803
|
_pendingItems = [];
|
|
5965
7804
|
_pendingSpaceId = null;
|
|
5966
7805
|
_flushTimer = null;
|
|
7806
|
+
_legacy = false;
|
|
5967
7807
|
/** @internal Injected by CueApi after construction to avoid circular dependency. */
|
|
5968
7808
|
_bindApi(api) {
|
|
5969
7809
|
this._api = api;
|
|
5970
7810
|
}
|
|
5971
7811
|
/**
|
|
5972
|
-
*
|
|
5973
|
-
*
|
|
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.
|
|
5974
7819
|
*/
|
|
5975
|
-
async
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
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;
|
|
5979
7830
|
const existing = await _loadPending(spaceId);
|
|
5980
7831
|
if (!existing || existing.items.length === 0)
|
|
5981
7832
|
return;
|
|
@@ -5985,7 +7836,7 @@ var CueSyncApi = class {
|
|
|
5985
7836
|
try {
|
|
5986
7837
|
this._pendingSpaceId = spaceId;
|
|
5987
7838
|
this._pendingItems = [];
|
|
5988
|
-
await this._flushBatch(existing.items, spaceId,
|
|
7839
|
+
await this._flushBatch(existing.items, spaceId, verbose);
|
|
5989
7840
|
console.info("Metadata uploaded \u2705");
|
|
5990
7841
|
} catch (err) {
|
|
5991
7842
|
throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6006,7 +7857,7 @@ var CueSyncApi = class {
|
|
|
6006
7857
|
const tier = project?.projectSettings?.tier ?? "l";
|
|
6007
7858
|
const [remoteFiles, consumption, creditMap, tierNames] = await Promise.all([
|
|
6008
7859
|
this._listRemoteFiles(graph, spaceId, providerId, verbose),
|
|
6009
|
-
this._api?.getConsumption(spaceId
|
|
7860
|
+
this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance")),
|
|
6010
7861
|
this._fetchUnitCreditMap(verbose),
|
|
6011
7862
|
this._fetchTierNames()
|
|
6012
7863
|
]);
|
|
@@ -6040,18 +7891,17 @@ var CueSyncApi = class {
|
|
|
6040
7891
|
};
|
|
6041
7892
|
}
|
|
6042
7893
|
async sync(localFiles, options) {
|
|
6043
|
-
const { spaceId, providerId, userId, verbose, onProgress } = options;
|
|
7894
|
+
const { spaceId, providerId, userId, verbose, onProgress, legacy } = options;
|
|
7895
|
+
this._legacy = legacy ?? false;
|
|
6044
7896
|
const token = await this._auth.getToken();
|
|
6045
7897
|
if (!token)
|
|
6046
7898
|
throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
6047
7899
|
const graph = await this._getOrCreateGraph(spaceId, token);
|
|
6048
7900
|
if (verbose)
|
|
6049
7901
|
console.info("Listing remote files \u23F3");
|
|
6050
|
-
const project = await this._projects.getProject(spaceId);
|
|
6051
|
-
const tier = project?.projectSettings?.tier ?? "l";
|
|
6052
7902
|
const [remoteFiles, consumption] = await Promise.all([
|
|
6053
7903
|
this._listRemoteFiles(graph, spaceId, providerId, verbose),
|
|
6054
|
-
this._api?.getConsumption(spaceId
|
|
7904
|
+
this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
|
|
6055
7905
|
]);
|
|
6056
7906
|
const { unitsAvailable } = consumption;
|
|
6057
7907
|
const report = await compareLocalRemote(localFiles, remoteFiles);
|
|
@@ -6076,7 +7926,7 @@ var CueSyncApi = class {
|
|
|
6076
7926
|
);
|
|
6077
7927
|
}
|
|
6078
7928
|
}
|
|
6079
|
-
await this._initPendingBatch(spaceId,
|
|
7929
|
+
await this._initPendingBatch(spaceId, verbose);
|
|
6080
7930
|
if (verbose && toUpload.length)
|
|
6081
7931
|
console.info("Syncing missing files \u23F3");
|
|
6082
7932
|
for (const file of toUpload) {
|
|
@@ -6090,10 +7940,10 @@ var CueSyncApi = class {
|
|
|
6090
7940
|
);
|
|
6091
7941
|
if (!rawMeta.blob_name)
|
|
6092
7942
|
throw new Error(`blob_name missing for ${file.relativePath}`);
|
|
6093
|
-
const fileBuffer =
|
|
7943
|
+
const fileBuffer = file.data ?? new Uint8Array(await _readNodeFile(file.fullPath));
|
|
6094
7944
|
await this._blob.uploadRaw(
|
|
6095
7945
|
rawMeta.blob_name,
|
|
6096
|
-
|
|
7946
|
+
fileBuffer,
|
|
6097
7947
|
rawMeta
|
|
6098
7948
|
);
|
|
6099
7949
|
await this._queueFileLocation({
|
|
@@ -6131,7 +7981,7 @@ var CueSyncApi = class {
|
|
|
6131
7981
|
}
|
|
6132
7982
|
await this._drainPending(verbose);
|
|
6133
7983
|
this._stopFlushTimer();
|
|
6134
|
-
const postSyncConsumption = await (this._api?.getConsumption(spaceId
|
|
7984
|
+
const postSyncConsumption = await (this._api?.getConsumption(spaceId) ?? Promise.resolve({ creditsAvailable: 0 }));
|
|
6135
7985
|
return {
|
|
6136
7986
|
syncCount,
|
|
6137
7987
|
syncSize,
|
|
@@ -6224,7 +8074,11 @@ WHERE {
|
|
|
6224
8074
|
}
|
|
6225
8075
|
return map;
|
|
6226
8076
|
}
|
|
6227
|
-
async _initPendingBatch(spaceId,
|
|
8077
|
+
async _initPendingBatch(spaceId, verbose) {
|
|
8078
|
+
if (this._flushTimer !== null) {
|
|
8079
|
+
clearInterval(this._flushTimer);
|
|
8080
|
+
this._flushTimer = null;
|
|
8081
|
+
}
|
|
6228
8082
|
this._pendingSpaceId = spaceId;
|
|
6229
8083
|
this._pendingItems = [];
|
|
6230
8084
|
const existing = await _loadPending(spaceId);
|
|
@@ -6233,7 +8087,7 @@ WHERE {
|
|
|
6233
8087
|
if (verbose)
|
|
6234
8088
|
console.info(`Flushing ${existing.items.length} pending file location(s) from previous sync \u23F3`);
|
|
6235
8089
|
try {
|
|
6236
|
-
await this._flushBatch(existing.items, spaceId,
|
|
8090
|
+
await this._flushBatch(existing.items, spaceId, verbose);
|
|
6237
8091
|
console.info("Metadata uploaded \u2705");
|
|
6238
8092
|
} catch (err) {
|
|
6239
8093
|
throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6255,22 +8109,29 @@ WHERE {
|
|
|
6255
8109
|
await _savePending({ spaceId: this._pendingSpaceId, items: this._pendingItems });
|
|
6256
8110
|
}
|
|
6257
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
|
+
}
|
|
6258
8120
|
async _drainPending(verbose) {
|
|
6259
8121
|
if (!this._pendingSpaceId || this._pendingItems.length === 0)
|
|
6260
8122
|
return;
|
|
6261
|
-
|
|
6262
|
-
if (!token)
|
|
8123
|
+
if (!this._auth.currentUser)
|
|
6263
8124
|
return;
|
|
6264
|
-
await this._flushBatch(this._pendingItems, this._pendingSpaceId,
|
|
8125
|
+
await this._flushBatch(this._pendingItems, this._pendingSpaceId, verbose);
|
|
6265
8126
|
}
|
|
6266
|
-
async _flushBatch(items, spaceId,
|
|
8127
|
+
async _flushBatch(items, spaceId, verbose) {
|
|
6267
8128
|
const snapshot = [...items];
|
|
6268
8129
|
if (this._pendingSpaceId === spaceId)
|
|
6269
8130
|
this._pendingItems = [];
|
|
6270
8131
|
await _clearPending(spaceId);
|
|
6271
8132
|
try {
|
|
6272
8133
|
for (let i = 0; i < snapshot.length; i += FSS_BATCH_CHUNK_SIZE) {
|
|
6273
|
-
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);
|
|
6274
8135
|
}
|
|
6275
8136
|
if (verbose)
|
|
6276
8137
|
console.info(`Wrote ${snapshot.length} file location(s) to commands API \u2705`);
|
|
@@ -6281,15 +8142,15 @@ WHERE {
|
|
|
6281
8142
|
throw err;
|
|
6282
8143
|
}
|
|
6283
8144
|
}
|
|
6284
|
-
async _postFssBatch(items, spaceId
|
|
8145
|
+
async _postFssBatch(items, spaceId) {
|
|
6285
8146
|
const controller = new AbortController();
|
|
6286
8147
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
8148
|
+
const url = this._legacy ? `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}?blob=true` : `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`;
|
|
6287
8149
|
let response;
|
|
6288
8150
|
try {
|
|
6289
|
-
response = await
|
|
8151
|
+
response = await this._auth.authenticatedFetch(url, {
|
|
6290
8152
|
method: "POST",
|
|
6291
8153
|
headers: {
|
|
6292
|
-
Authorization: `Bearer ${token}`,
|
|
6293
8154
|
"Content-Type": "application/json",
|
|
6294
8155
|
"x-project-id": spaceId
|
|
6295
8156
|
},
|
|
@@ -6333,7 +8194,8 @@ WHERE {
|
|
|
6333
8194
|
const entries = await Promise.all(
|
|
6334
8195
|
batch.map(async (f) => ({
|
|
6335
8196
|
originalPath: f.relativePath,
|
|
6336
|
-
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))
|
|
6337
8199
|
}))
|
|
6338
8200
|
);
|
|
6339
8201
|
const records = _scanFn(entries);
|
|
@@ -6350,6 +8212,94 @@ WHERE {
|
|
|
6350
8212
|
}
|
|
6351
8213
|
return Array.from(merged.values());
|
|
6352
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
|
+
}
|
|
6353
8303
|
async _fetchTierNames() {
|
|
6354
8304
|
try {
|
|
6355
8305
|
const text = await this._blob.downloadPublic("tier-names.json");
|
|
@@ -6381,28 +8331,201 @@ WHERE {
|
|
|
6381
8331
|
}
|
|
6382
8332
|
};
|
|
6383
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
|
+
|
|
6384
8489
|
// libs/js/cue-sdk/src/lib/cue-node.ts
|
|
6385
|
-
var
|
|
6386
|
-
var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
|
|
6387
|
-
var BUCKET_PUBLIC2 = "cue_public_eu_west6";
|
|
8490
|
+
var import_storage6 = require("firebase/storage");
|
|
6388
8491
|
var CueNode = class extends Cue {
|
|
6389
8492
|
constructor(config) {
|
|
6390
8493
|
super(config);
|
|
6391
8494
|
}
|
|
6392
8495
|
_buildApi(projects) {
|
|
6393
|
-
const
|
|
6394
|
-
const
|
|
6395
|
-
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);
|
|
6396
8502
|
if (this._isEmulator) {
|
|
6397
8503
|
const storageHost = this._endpoints.storageEmulatorHost;
|
|
6398
8504
|
const storagePort = this._endpoints.storageEmulatorPort;
|
|
6399
|
-
(0,
|
|
6400
|
-
(0,
|
|
6401
|
-
(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);
|
|
6402
8508
|
}
|
|
6403
|
-
const blob = new CueBlobStorage({
|
|
6404
|
-
|
|
6405
|
-
|
|
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
|
+
);
|
|
6406
8529
|
syncApi._bindApi(api);
|
|
6407
8530
|
return api;
|
|
6408
8531
|
}
|
|
@@ -6423,7 +8546,8 @@ async function authenticate(emulators, space, key, verbose = false) {
|
|
|
6423
8546
|
apiKey: FIREBASE_CONFIG().apiKey,
|
|
6424
8547
|
appId: FIREBASE_CONFIG().appId,
|
|
6425
8548
|
measurementId: FIREBASE_CONFIG().measurementId,
|
|
6426
|
-
environment: emulators ? "emulator" : "production"
|
|
8549
|
+
environment: emulators ? "emulator" : "production",
|
|
8550
|
+
...emulators ? { endpoints: getEmulatorEndpoints() } : {}
|
|
6427
8551
|
});
|
|
6428
8552
|
if (verbose)
|
|
6429
8553
|
console.info("Authenticating \u23F3");
|
|
@@ -6443,7 +8567,7 @@ async function compareHandler(options) {
|
|
|
6443
8567
|
await authenticate(emulators, space, options.key, verbose);
|
|
6444
8568
|
if (verbose)
|
|
6445
8569
|
console.info("Building compare base \u23F3");
|
|
6446
|
-
const qh = async (
|
|
8570
|
+
const qh = async (query4) => queryHandler(query4, space, emulators);
|
|
6447
8571
|
const [localFiles, remoteFiles] = await Promise.all([
|
|
6448
8572
|
listLocalFiles(
|
|
6449
8573
|
path,
|
|
@@ -6543,23 +8667,23 @@ async function compareHandler(options) {
|
|
|
6543
8667
|
}
|
|
6544
8668
|
|
|
6545
8669
|
// apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
|
|
6546
|
-
var
|
|
6547
|
-
var
|
|
6548
|
-
var
|
|
8670
|
+
var import_storage7 = require("firebase/storage");
|
|
8671
|
+
var import_path2 = require("path");
|
|
8672
|
+
var import_promises = require("fs/promises");
|
|
6549
8673
|
async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
|
|
6550
8674
|
const firebase = CueFirebase.getInstance();
|
|
6551
8675
|
const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
|
|
6552
8676
|
console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
|
|
6553
|
-
const listResult = await (0,
|
|
6554
|
-
const outputDir = (0,
|
|
6555
|
-
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 });
|
|
6556
8680
|
for (const fileRef of listResult.items) {
|
|
6557
8681
|
console.log(fileRef.name);
|
|
6558
8682
|
if (subDir && !fileRef.name.includes(subString))
|
|
6559
8683
|
continue;
|
|
6560
|
-
const bytes = await (0,
|
|
6561
|
-
const outputPath = (0,
|
|
6562
|
-
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));
|
|
6563
8687
|
console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
|
|
6564
8688
|
}
|
|
6565
8689
|
}
|
|
@@ -6584,12 +8708,12 @@ async function dumpProcessedHandler(options) {
|
|
|
6584
8708
|
}
|
|
6585
8709
|
|
|
6586
8710
|
// apps/desktop/cue-cli/src/helpers/graph-dump.ts
|
|
6587
|
-
var
|
|
8711
|
+
var import_fs = require("fs");
|
|
6588
8712
|
var import_stream = require("stream");
|
|
6589
|
-
var
|
|
6590
|
-
var
|
|
6591
|
-
var
|
|
6592
|
-
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");
|
|
6593
8717
|
var import_auth9 = require("firebase/auth");
|
|
6594
8718
|
|
|
6595
8719
|
// libs/js/size-tools/src/lib/js-size-tools.ts
|
|
@@ -6609,7 +8733,7 @@ function humanFileSize(bytes, si = false, dp = 1) {
|
|
|
6609
8733
|
}
|
|
6610
8734
|
|
|
6611
8735
|
// apps/desktop/cue-cli/src/helpers/graph-dump.ts
|
|
6612
|
-
var
|
|
8736
|
+
var import_fs3 = require("fs");
|
|
6613
8737
|
async function dumpRdfGraphToFileJelly(spaceId, useEmulator = false, verbose = false) {
|
|
6614
8738
|
return dumpRdfGraphToFile(
|
|
6615
8739
|
spaceId,
|
|
@@ -6625,7 +8749,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6625
8749
|
if (verbose)
|
|
6626
8750
|
console.info(`Streaming RDF graph \u23F3`);
|
|
6627
8751
|
const filePath = outFile || `${spaceId}.nq`;
|
|
6628
|
-
if ((0,
|
|
8752
|
+
if ((0, import_fs3.existsSync)(filePath) || (0, import_fs3.existsSync)(`${filePath}.gz`)) {
|
|
6629
8753
|
if (verbose)
|
|
6630
8754
|
console.info(`File ${filePath} already exists, skipping download.`);
|
|
6631
8755
|
return mimeType === "application/n-quads" ? `${filePath}.gz` : filePath;
|
|
@@ -6642,7 +8766,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6642
8766
|
if (!res.ok || !res.body) {
|
|
6643
8767
|
throw new Error(`Failed to stream data: ${res.status} ${res.statusText}`);
|
|
6644
8768
|
}
|
|
6645
|
-
const fileStream = (0,
|
|
8769
|
+
const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
|
|
6646
8770
|
const nodeStream = import_stream.Readable.fromWeb(res.body);
|
|
6647
8771
|
let chunkCount = 0;
|
|
6648
8772
|
let totalSize = 0;
|
|
@@ -6669,7 +8793,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
|
|
|
6669
8793
|
}
|
|
6670
8794
|
}, streamTimeout);
|
|
6671
8795
|
try {
|
|
6672
|
-
await (0,
|
|
8796
|
+
await (0, import_promises2.pipeline)(nodeStream, fileStream);
|
|
6673
8797
|
} finally {
|
|
6674
8798
|
if (hasDataTimeout) {
|
|
6675
8799
|
clearTimeout(hasDataTimeout);
|
|
@@ -6702,7 +8826,7 @@ async function dumpRdfGraphToFileConstruct(spaceId, useEmulator = false, verbose
|
|
|
6702
8826
|
WHERE { GRAPH ${graph} { ?s ?p ?o } }
|
|
6703
8827
|
`;
|
|
6704
8828
|
let offset = 0;
|
|
6705
|
-
const fileStream = (0,
|
|
8829
|
+
const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
|
|
6706
8830
|
let hasMore = true;
|
|
6707
8831
|
while (hasMore) {
|
|
6708
8832
|
const queries = Array.from(
|
|
@@ -6767,14 +8891,14 @@ async function retryWithBackoff(fn, maxRetries, delayMs, label) {
|
|
|
6767
8891
|
async function _doGzip(filePath) {
|
|
6768
8892
|
const gzFilePath = `${filePath}.gz`;
|
|
6769
8893
|
await new Promise((resolve2, reject) => {
|
|
6770
|
-
const gzip = (0,
|
|
6771
|
-
const source = (0,
|
|
6772
|
-
const dest = (0,
|
|
6773
|
-
(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);
|
|
6774
8898
|
});
|
|
6775
|
-
await (0,
|
|
8899
|
+
await (0, import_promises3.unlink)(filePath);
|
|
6776
8900
|
}
|
|
6777
|
-
async function _doQuery(
|
|
8901
|
+
async function _doQuery(query4, spaceId, url) {
|
|
6778
8902
|
const token = await (0, import_auth9.getAuth)().currentUser?.getIdToken();
|
|
6779
8903
|
try {
|
|
6780
8904
|
const res = await fetch(url, {
|
|
@@ -6785,7 +8909,7 @@ async function _doQuery(query3, spaceId, url) {
|
|
|
6785
8909
|
"Content-Type": "application/sparql-query",
|
|
6786
8910
|
Accept: "application/n-quads"
|
|
6787
8911
|
},
|
|
6788
|
-
body:
|
|
8912
|
+
body: query4
|
|
6789
8913
|
});
|
|
6790
8914
|
if (!res.ok) {
|
|
6791
8915
|
console.error(`Error: ${res.status} ${res.statusText}`);
|
|
@@ -6864,9 +8988,9 @@ function _getTemplate(partition, base = "fuseki-base-new/fuseki") {
|
|
|
6864
8988
|
|
|
6865
8989
|
// apps/desktop/cue-cli/src/helpers/graph-upload.ts
|
|
6866
8990
|
var import_auth11 = require("firebase/auth");
|
|
6867
|
-
var
|
|
8991
|
+
var import_fs4 = require("fs");
|
|
6868
8992
|
async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
|
|
6869
|
-
const stream = (0,
|
|
8993
|
+
const stream = (0, import_fs4.createReadStream)(filePath);
|
|
6870
8994
|
const token = await (0, import_auth11.getAuth)().currentUser?.getIdToken();
|
|
6871
8995
|
const dataUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", "/data");
|
|
6872
8996
|
const headers = {
|
|
@@ -6893,7 +9017,7 @@ async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads"
|
|
|
6893
9017
|
|
|
6894
9018
|
// apps/desktop/cue-cli/src/cue-cli-dump.ts
|
|
6895
9019
|
async function dumpHandler(options) {
|
|
6896
|
-
const { space, verbose, emulators, jelly, query:
|
|
9020
|
+
const { space, verbose, emulators, jelly, query: query4, load } = options;
|
|
6897
9021
|
try {
|
|
6898
9022
|
const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
|
|
6899
9023
|
if (!isSuperAdmin) {
|
|
@@ -6914,7 +9038,7 @@ async function dumpHandler(options) {
|
|
|
6914
9038
|
} else {
|
|
6915
9039
|
if (verbose)
|
|
6916
9040
|
console.time("Downloaded and zipped graph \u2705");
|
|
6917
|
-
if (
|
|
9041
|
+
if (query4) {
|
|
6918
9042
|
if (verbose)
|
|
6919
9043
|
console.info("Setting: Construct query");
|
|
6920
9044
|
filePath = await dumpRdfGraphToFileConstruct(space, emulators, verbose);
|
|
@@ -6951,16 +9075,16 @@ async function dumpHandler(options) {
|
|
|
6951
9075
|
}
|
|
6952
9076
|
|
|
6953
9077
|
// apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
|
|
6954
|
-
var
|
|
9078
|
+
var import_storage8 = require("firebase/storage");
|
|
6955
9079
|
async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
6956
9080
|
const firebase = CueFirebase.getInstance();
|
|
6957
9081
|
const storage = firebase.storageProcessed;
|
|
6958
9082
|
console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
|
|
6959
|
-
const listResult = await (0,
|
|
9083
|
+
const listResult = await (0, import_storage8.listAll)((0, import_storage8.ref)(storage, `${space}/triples`));
|
|
6960
9084
|
for (const fileRef of listResult.items) {
|
|
6961
9085
|
if (subString && !fileRef.name.match(subString))
|
|
6962
9086
|
continue;
|
|
6963
|
-
const stream = await (0,
|
|
9087
|
+
const stream = await (0, import_storage8.getStream)(fileRef);
|
|
6964
9088
|
const reader = stream.getReader();
|
|
6965
9089
|
const chunks = [];
|
|
6966
9090
|
let done = false;
|
|
@@ -6984,13 +9108,13 @@ async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
|
6984
9108
|
const buffer = Buffer.from(fileContent, "utf8");
|
|
6985
9109
|
let existingMetadata = {};
|
|
6986
9110
|
try {
|
|
6987
|
-
existingMetadata = await (0,
|
|
9111
|
+
existingMetadata = await (0, import_storage8.getMetadata)(fileRef);
|
|
6988
9112
|
} catch (err) {
|
|
6989
9113
|
console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
|
|
6990
9114
|
}
|
|
6991
9115
|
const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
|
|
6992
9116
|
const metadata = { customMetadata };
|
|
6993
|
-
await (0,
|
|
9117
|
+
await (0, import_storage8.uploadBytesResumable)((0, import_storage8.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
|
|
6994
9118
|
console.log(`Fixed ${fileRef.name} \u2705`);
|
|
6995
9119
|
} else {
|
|
6996
9120
|
console.log(`No changes for ${fileRef.name}`);
|
|
@@ -7017,10 +9141,11 @@ async function repairTtlHandler(options) {
|
|
|
7017
9141
|
}
|
|
7018
9142
|
|
|
7019
9143
|
// apps/desktop/cue-cli/src/cue-cli-sync.ts
|
|
7020
|
-
var
|
|
7021
|
-
var
|
|
7022
|
-
var
|
|
9144
|
+
var import_promises4 = require("fs/promises");
|
|
9145
|
+
var import_fs5 = require("fs");
|
|
9146
|
+
var import_path3 = require("path");
|
|
7023
9147
|
var readline = __toESM(require("readline"));
|
|
9148
|
+
init_src();
|
|
7024
9149
|
function askConfirm(question) {
|
|
7025
9150
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7026
9151
|
return new Promise((resolve2) => {
|
|
@@ -7031,13 +9156,14 @@ function askConfirm(question) {
|
|
|
7031
9156
|
});
|
|
7032
9157
|
}
|
|
7033
9158
|
async function syncHandler(options) {
|
|
7034
|
-
const { space, path, verbose, provider, emulators, zip } = options;
|
|
9159
|
+
const { space, path, verbose, provider, emulators, zip, legacy } = options;
|
|
7035
9160
|
try {
|
|
7036
9161
|
const cue = new CueNode({
|
|
7037
9162
|
apiKey: FIREBASE_CONFIG().apiKey,
|
|
7038
9163
|
appId: FIREBASE_CONFIG().appId,
|
|
7039
9164
|
measurementId: FIREBASE_CONFIG().measurementId,
|
|
7040
|
-
environment: emulators ? "emulator" : "production"
|
|
9165
|
+
environment: emulators ? "emulator" : "production",
|
|
9166
|
+
...emulators ? { endpoints: getEmulatorEndpoints() } : {}
|
|
7041
9167
|
});
|
|
7042
9168
|
const key = options.key ?? process.env.CUE_API_KEY;
|
|
7043
9169
|
if (!key) {
|
|
@@ -7048,15 +9174,15 @@ async function syncHandler(options) {
|
|
|
7048
9174
|
}
|
|
7049
9175
|
if (verbose)
|
|
7050
9176
|
console.info("Building sync base \u23F3");
|
|
7051
|
-
const resolvedPath = (0,
|
|
7052
|
-
const pathStat = await (0,
|
|
9177
|
+
const resolvedPath = (0, import_path3.resolve)(path);
|
|
9178
|
+
const pathStat = await (0, import_promises4.stat)(resolvedPath);
|
|
7053
9179
|
const isFile = pathStat.isFile();
|
|
7054
9180
|
let localFiles;
|
|
7055
9181
|
if (isFile) {
|
|
7056
9182
|
if (verbose)
|
|
7057
9183
|
console.info(`Path is a file, syncing single file: ${resolvedPath}`);
|
|
7058
|
-
const md5 = await fromReadStream((0,
|
|
7059
|
-
const relativePath = (0,
|
|
9184
|
+
const md5 = await fromReadStream((0, import_fs5.createReadStream)(resolvedPath));
|
|
9185
|
+
const relativePath = (0, import_path3.basename)(resolvedPath);
|
|
7060
9186
|
const contentUUID = contextBasedGuid(md5);
|
|
7061
9187
|
const locationUUID = generateFileUUID(relativePath, provider);
|
|
7062
9188
|
localFiles = [
|
|
@@ -7117,7 +9243,7 @@ async function syncHandler(options) {
|
|
|
7117
9243
|
console.info(` Credits required: ${Math.round(preview.creditsToConsume)}`);
|
|
7118
9244
|
console.info(` Credits available: ${Math.round(preview.creditsAvailable)}
|
|
7119
9245
|
`);
|
|
7120
|
-
await cue.api.sync.flushPendingMetadata(space, verbose);
|
|
9246
|
+
await cue.api.sync.flushPendingMetadata(space, verbose, legacy);
|
|
7121
9247
|
if (preview.filesToUpload === 0) {
|
|
7122
9248
|
console.info("Everything is already synced.");
|
|
7123
9249
|
process.exit(0);
|
|
@@ -7138,6 +9264,7 @@ async function syncHandler(options) {
|
|
|
7138
9264
|
providerId: provider,
|
|
7139
9265
|
userId: user.uid,
|
|
7140
9266
|
verbose,
|
|
9267
|
+
legacy,
|
|
7141
9268
|
onProgress: ({ percent, syncCount, totalCount }) => {
|
|
7142
9269
|
const filled = Math.round(percent / 5);
|
|
7143
9270
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
@@ -7179,8 +9306,8 @@ async function syncHandler(options) {
|
|
|
7179
9306
|
}
|
|
7180
9307
|
|
|
7181
9308
|
// apps/desktop/cue-cli/src/cue-cli-util-remove-rdf-star.ts
|
|
7182
|
-
var
|
|
7183
|
-
var
|
|
9309
|
+
var import_fs6 = require("fs");
|
|
9310
|
+
var import_zlib2 = require("zlib");
|
|
7184
9311
|
|
|
7185
9312
|
// libs/js/rdf-tools/src/lib/nq-to-nt.ts
|
|
7186
9313
|
var import_n37 = require("n3");
|
|
@@ -7227,8 +9354,8 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7227
9354
|
console.info(`Input: ${input} (${isGzipped ? "gzipped" : "plain"})`);
|
|
7228
9355
|
if (verbose)
|
|
7229
9356
|
console.info(`Output: ${output}`);
|
|
7230
|
-
const fileStream = (0,
|
|
7231
|
-
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;
|
|
7232
9359
|
let removed = 0;
|
|
7233
9360
|
const cleanStream = removeRDFStar(inputStream, (count) => {
|
|
7234
9361
|
removed = count;
|
|
@@ -7236,9 +9363,9 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7236
9363
|
console.info(`Removed RDF-star triples so far: ${count}`);
|
|
7237
9364
|
}
|
|
7238
9365
|
});
|
|
7239
|
-
const writeStream = (0,
|
|
9366
|
+
const writeStream = (0, import_fs6.createWriteStream)(output);
|
|
7240
9367
|
if (isGzipped) {
|
|
7241
|
-
const gzip = (0,
|
|
9368
|
+
const gzip = (0, import_zlib2.createGzip)();
|
|
7242
9369
|
gzip.pipe(writeStream);
|
|
7243
9370
|
for await (const chunk of cleanStream) {
|
|
7244
9371
|
gzip.write(chunk);
|
|
@@ -7263,7 +9390,7 @@ async function utilRemoveRdfStarHandler(options) {
|
|
|
7263
9390
|
}
|
|
7264
9391
|
|
|
7265
9392
|
// apps/desktop/cue-cli/src/cue-cli-util-rdf-compare.ts
|
|
7266
|
-
var
|
|
9393
|
+
var import_fs7 = require("fs");
|
|
7267
9394
|
|
|
7268
9395
|
// libs/js/rdf-compare/src/lib/js-rdf-compare.ts
|
|
7269
9396
|
var import_n39 = require("n3");
|
|
@@ -7332,8 +9459,8 @@ async function utilRdfCompareHandler(options) {
|
|
|
7332
9459
|
console.info(`File 1: ${file1}`);
|
|
7333
9460
|
if (verbose)
|
|
7334
9461
|
console.info(`File 2: ${file2}`);
|
|
7335
|
-
const content1 = (0,
|
|
7336
|
-
const content2 = (0,
|
|
9462
|
+
const content1 = (0, import_fs7.readFileSync)(file1, "utf8");
|
|
9463
|
+
const content2 = (0, import_fs7.readFileSync)(file2, "utf8");
|
|
7337
9464
|
const result = await compareTTL(content1, content2);
|
|
7338
9465
|
console.info(`File 1 triple count: ${result.file1TripleCount}`);
|
|
7339
9466
|
console.info(`File 2 triple count: ${result.file2TripleCount}`);
|
|
@@ -7361,10 +9488,10 @@ Triples only in file 2 (${result.triplesOnlyInFile2.size}):`);
|
|
|
7361
9488
|
// apps/desktop/cue-cli/src/main.ts
|
|
7362
9489
|
var packageJson;
|
|
7363
9490
|
try {
|
|
7364
|
-
packageJson = JSON.parse((0,
|
|
9491
|
+
packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "package.json"), "utf8"));
|
|
7365
9492
|
} catch {
|
|
7366
9493
|
try {
|
|
7367
|
-
packageJson = JSON.parse((0,
|
|
9494
|
+
packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "../package.json"), "utf8"));
|
|
7368
9495
|
} catch {
|
|
7369
9496
|
packageJson = { version: "0.0.0" };
|
|
7370
9497
|
console.warn("Could not find package.json, using fallback version");
|
|
@@ -7372,7 +9499,7 @@ try {
|
|
|
7372
9499
|
}
|
|
7373
9500
|
var program = new import_commander.Command();
|
|
7374
9501
|
program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
|
|
7375
|
-
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);
|
|
7376
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);
|
|
7377
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);
|
|
7378
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);
|