@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.
Files changed (2) hide show
  1. package/main.js +2958 -831
  2. 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 import_fs9 = require("fs");
97
- var import_path6 = require("path");
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 ?? "8093";
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 import_storage = require("firebase/storage");
228
- var import_firestore = require("firebase/firestore");
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
- var CueFirebase = class _CueFirebase {
232
- static _instance;
233
- _muted = true;
234
- _emulator = false;
235
- constructor(config, auth, muted = false) {
236
- this._muted = muted;
237
- this._init(config, auth);
238
- }
239
- static getInstance(config, auth, muted = false) {
240
- if (!_CueFirebase._instance) {
241
- if (config === void 0)
242
- throw new Error("Config needed for instantiation!");
243
- if (!muted)
244
- console.info("Creating new CueFirebase instance");
245
- _CueFirebase._instance = new _CueFirebase(config, auth, muted);
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
- _functionAcceptTerms;
250
- _functionEmitMessage;
251
- _functionGetUserInfo;
252
- _functionChangeUserRoleOnProject;
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
- get functionEmitMessage() {
275
- return this._functionEmitMessage;
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
- get functionGetUserInfo() {
278
- return this._functionGetUserInfo;
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
- get functionInviteUserToProject() {
281
- return this._functionInviteUserToProject;
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, import_storage.getStorage)(app, BUCKET_PROCESSED);
374
- this._storageRaw = (0, import_storage.getStorage)(app, BUCKET_RAW);
375
- this._storageChatSessions = (0, import_storage.getStorage)(app, BUCKET_CHAT_SESSIONS);
376
- this._storageLogs = (0, import_storage.getStorage)(app, BUCKET_LOGS);
377
- this._storagePublic = (0, import_storage.getStorage)(app, BUCKET_PUBLIC);
378
- this._storagePersistence = (0, import_storage.getStorage)(app, BUCKET_PERSISTENCE);
379
- this._collectionChatSessions = (0, import_firestore.collection)(
380
- (0, import_firestore.getFirestore)(app),
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, import_firestore.collection)(
384
- (0, import_firestore.getFirestore)(app),
1384
+ this._collectionOrganizations = (0, import_firestore2.collection)(
1385
+ (0, import_firestore2.getFirestore)(app),
385
1386
  COLLECTION_ORGANIZATIONS
386
1387
  );
387
- this._collectionProjects = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_PROJECTS);
388
- this._collectionRDFWriting = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_RDF_WRITING);
389
- this._collectionAPIKeys = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_API_KEYS);
390
- this._collectionUsers = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_USERS);
391
- this._collectionUserTermsAcceptance = (0, import_firestore.collection)(
392
- (0, import_firestore.getFirestore)(app),
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, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_TIERS);
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, import_firestore.connectFirestoreEmulator)((0, import_firestore.getFirestore)(this._app), firestoreHost, firestorePort);
1452
+ (0, import_firestore2.connectFirestoreEmulator)((0, import_firestore2.getFirestore)(this._app), firestoreHost, firestorePort);
443
1453
  (0, import_functions.connectFunctionsEmulator)(functions, "localhost", 5001);
444
- (0, import_storage.connectStorageEmulator)(this._storageProcessed, storageHost, storagePort);
445
- (0, import_storage.connectStorageEmulator)(this._storageRaw, storageHost, storagePort);
446
- (0, import_storage.connectStorageEmulator)(this._storageChatSessions, storageHost, storagePort);
447
- (0, import_storage.connectStorageEmulator)(this._storageLogs, storageHost, storagePort);
448
- (0, import_storage.connectStorageEmulator)(this._storagePublic, storageHost, storagePort);
449
- (0, import_storage.connectStorageEmulator)(this._storagePersistence, storageHost, storagePort);
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 import_storage2 = require("firebase/storage");
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, import_storage2.ref)(storage, spaceId);
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, import_storage2.listAll)(listRef),
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
- var import_path2 = require("path");
3774
-
3775
- // libs/js/id-builders/src/lib/id-builders.ts
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
- // libs/js/id-builders/src/lib/md5-builder.ts
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 hashes = hashWorkerPath ? await md5FromWorker(fullPaths, hashWorkerPath, verbose, logIntervalPct) : await md5NoWorker(fullPaths, verbose);
3935
- const stats = await Promise.all(fullPaths.map((f) => (0, import_promises.stat)(f)));
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 = (0, import_path2.relative)(dir, f);
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 (0, import_promises.readdir)(currentDir, { withFileTypes: true });
4843
+ const entries = await readdir(currentDir, { withFileTypes: true });
3950
4844
  for (const entry of entries) {
3951
- const fullPath = (0, import_path2.join)(currentDir, entry.name);
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 (0, import_promises2.mkdir)(targetDir, { recursive: true });
3995
- const data = await (0, import_promises.readFile)(zipPath);
3996
- const zip = await import_jszip.default.loadAsync(data);
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 = (0, import_path2.join)(targetDir, ...pathParts.slice(0, -1));
4002
- const targetPath = (0, import_path2.join)(fileTargetDir, pathParts[pathParts.length - 1]);
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 (0, import_promises.writeFile)(targetPath, content);
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
- await (0, import_promises2.mkdir)(path, { recursive: true });
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 (0, import_promises.rm)(p, { recursive: true, force: true });
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(query3, spaceId, useEmulator) {
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: query3
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 headers = await this._authHeaders();
4173
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SEARCH}`, {
4174
- method: "POST",
4175
- headers,
4176
- body: JSON.stringify({
4177
- term: request.term,
4178
- projectId: request.projectId,
4179
- categories: request.categories ?? []
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(`Search request failed: ${response.status} ${response.statusText}`);
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(query3, projectId) {
4192
- const headers = await this._authHeaders();
4193
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SPARQL}`, {
4194
- method: "POST",
4195
- headers,
4196
- body: JSON.stringify({ query: query3, projectId })
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(`SPARQL query failed: ${response.status} ${response.statusText}`);
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, tier) {
4204
- const headers = await this._authHeaders();
4205
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`, {
4206
- headers: {
4207
- ...headers,
4208
- "x-project-id": projectId,
4209
- ...tier ? { "x-tier": tier } : {}
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(`Failed to fetch consumption: ${response.status} ${response.statusText}`);
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 import_firestore2 = require("firebase/firestore");
4221
- var COLLECTION_PROJECTS2 = "projects";
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, import_firestore2.getFirestore)(app);
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, import_firestore2.connectFirestoreEmulator)(this._db, host, port);
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 userId = this._requireUser();
4245
- const projectId = options.id ?? crypto.randomUUID();
4246
- const now = (/* @__PURE__ */ new Date()).toISOString();
4247
- const projectSettings = { views: [], chatDisabled: false };
4248
- const ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4249
- const existing = await (0, import_firestore2.getDoc)(ref5);
4250
- if (existing.exists()) {
4251
- throw new Error(`Project with ID "${projectId}" already exists.`);
4252
- }
4253
- const data = {
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, syncers, or admins array.
4271
- * Runs three parallel Firestore queries and deduplicates by project ID.
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, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2);
4276
- const [memberSnap, syncerSnap, adminSnap] = await Promise.all([
4277
- (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("members", "array-contains", userId))),
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 ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4297
- const snap = await (0, import_firestore2.getDoc)(ref5);
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 ref5 = (0, import_firestore2.doc)(this._db, "clientSync", projectId);
4310
- await (0, import_firestore2.setDoc)(ref5, {
4311
- unitsConsumed: (0, import_firestore2.increment)(units),
4312
- lastUpdated: (0, import_firestore2.serverTimestamp)(),
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 import_firestore3 = require("firebase/firestore");
4321
- var COLLECTION_API_KEYS2 = "apiKeys";
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, import_firestore3.getFirestore)(app);
5442
+ this._db = (0, import_firestore4.getFirestore)(app);
5443
+ this._functions = (0, import_functions3.getFunctions)(app, GCP_REGION2);
4326
5444
  if (useEmulator) {
4327
- (0, import_firestore3.connectFirestoreEmulator)(this._db, emulatorHost, emulatorPort);
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, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4412
- this._apiKeyDocRef = await (0, import_firestore3.addDoc)(col, data);
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, import_firestore3.deleteDoc)(this._apiKeyDocRef);
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 doc2 = (await (0, import_firestore3.getDoc)(this._apiKeyDocRef)).data();
4427
- return { key: doc2.key, expiration: doc2.expiration };
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, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4431
- const q = (0, import_firestore3.query)(col, (0, import_firestore3.where)("uid", "==", uid), (0, import_firestore3.limit)(1));
4432
- const snapshot = await (0, import_firestore3.getDocs)(q);
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/cue.ts
4446
- var FIREBASE_PROJECT_ID = "qaecy-mvp-406413";
4447
- var FIREBASE_SENDER_ID = "734737865998";
4448
- var ENDPOINTS = {
4449
- production: {
4450
- gatewayUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app",
4451
- tokenUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token",
4452
- authEmulatorUrl: "http://localhost:9099",
4453
- storageEmulatorHost: "localhost",
4454
- storageEmulatorPort: 9199,
4455
- firestoreEmulatorHost: "localhost",
4456
- firestoreEmulatorPort: 8080
4457
- },
4458
- emulator: {
4459
- gatewayUrl: "http://localhost:8093",
4460
- tokenUrl: "http://localhost:8093/token",
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
- var Cue = class {
4469
- auth;
4470
- api;
4471
- profile;
4472
- _app;
4473
- _endpoints;
4474
- _isEmulator;
4475
- constructor(config) {
4476
- const env = config.environment ?? "production";
4477
- this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
4478
- this._isEmulator = env === "emulator";
4479
- this._app = (0, import_app2.initializeApp)({
4480
- apiKey: config.apiKey,
4481
- appId: config.appId,
4482
- measurementId: config.measurementId,
4483
- authDomain: `${FIREBASE_PROJECT_ID}.firebaseapp.com`,
4484
- projectId: FIREBASE_PROJECT_ID,
4485
- messagingSenderId: FIREBASE_SENDER_ID
4486
- });
4487
- this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
4488
- const projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
4489
- this.api = this._buildApi(projects);
4490
- this.profile = new CueProfile(
4491
- this.auth,
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
- /** Override in subclasses to provide a custom CueApi (e.g. with sync). */
4499
- _buildApi(projects) {
4500
- return new CueApi(this.auth, this._endpoints.gatewayUrl, projects);
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
- /** Convenience: get the current user's Firebase ID token */
4503
- getToken(forceRefresh = false) {
4504
- return this.auth.getToken(forceRefresh);
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/cue-node.ts
5673
+ // libs/js/cue-sdk/src/lib/cache.ts
4509
5674
  var import_storage4 = require("firebase/storage");
4510
-
4511
- // libs/js/databases/src/lib/graph/fuseki.ts
4512
- var Fuseki = class _Fuseki {
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
- async ping() {
4534
- const query3 = "ASK { }";
4535
- const res = await this.query(query3);
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 query(query3, accept = "application/sparql-results+json") {
4539
- let res;
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
- res = await fetch(this.queryEndpoint, {
4542
- headers: {
4543
- ...this.baseHeaders,
4544
- "Content-Type": "application/x-www-form-urlencoded",
4545
- Accept: accept
4546
- },
4547
- method: "POST",
4548
- body: new URLSearchParams({ query: query3 })
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
- throw new Error(
4552
- `Fuseki is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
4553
- );
5729
+ if (_isNotFound(err))
5730
+ return void 0;
5731
+ throw err;
4554
5732
  }
4555
- if (!res.ok) {
4556
- const body = await res.text();
4557
- throw new Error(`Fuseki query failed (HTTP ${res.status}): ${body}`);
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
- return await res.json();
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
- async subset(query3, accept = "text/turtle") {
4562
- const res = await fetch(this.queryEndpoint, {
4563
- headers: {
4564
- ...this.baseHeaders,
4565
- "Content-Type": "application/x-www-form-urlencoded",
4566
- Authorization: this.baseHeaders["Authorization"] || "",
4567
- Accept: accept
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
- method: "POST",
4570
- body: new URLSearchParams({ query: query3 })
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 update(update) {
4578
- const res = await fetch(this.updateEndpoint, {
4579
- headers: {
4580
- ...this.baseHeaders,
4581
- "Content-Type": "application/x-www-form-urlencoded"
4582
- },
4583
- method: "POST",
4584
- body: new URLSearchParams({ update })
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/databases/src/lib/graph/qlever.ts
4603
- var import_zlib = require("zlib");
4604
- var import_n3 = require("n3");
4605
- var QLeverLockedError = class extends Error {
4606
- constructor(body) {
4607
- super(`QLever is locked (rebuild in progress): ${body}`);
4608
- this.name = "QLeverLockedError";
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
- * Retry an async write operation on 423 Locked with exponential backoff + jitter.
4629
- * 423 means qlever accessor has an ongoing rebuild; we should wait and retry.
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
- static async _retryOnLocked(fn) {
4632
- const maxRetries = _QLever.LOCKED_MAX_RETRIES;
4633
- const baseDelayMs = _QLever.LOCKED_BASE_DELAY_MS;
4634
- let lastError;
4635
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
4636
- try {
4637
- return await fn();
4638
- } catch (err) {
4639
- lastError = err;
4640
- if (err instanceof QLeverLockedError && attempt < maxRetries) {
4641
- const jitter = 0.5 + Math.random();
4642
- const delay = baseDelayMs * Math.pow(2, attempt) * jitter;
4643
- await new Promise((resolve2) => setTimeout(resolve2, delay));
4644
- continue;
4645
- }
4646
- throw err;
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
- constructor(graphOptions) {
4652
- this.queryEndpoint = graphOptions.queryEndpoint;
4653
- this.updateEndpoint = graphOptions.updateEndpoint;
4654
- this.dataEndpoint = this.updateEndpoint.replace(/\/update$/, "/data");
4655
- this.baseHeaders = Object.fromEntries(
4656
- Object.entries(graphOptions.originalHeaders || {}).filter(
4657
- ([key]) => _QLever.RELEVANT_HEADER_KEYS.includes(key)
4658
- )
4659
- );
4660
- this.baseHeaders["x-user-roles"] = "admin";
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
- async ping() {
4663
- const query3 = "ASK { }";
4664
- const res = await this.query(query3);
4665
- return res.boolean;
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
- async query(query3, accept = "application/sparql-results+json") {
4668
- let res;
4669
- try {
4670
- res = await fetch(this.queryEndpoint, {
4671
- headers: {
4672
- ...this.baseHeaders,
4673
- "Content-Type": "application/x-www-form-urlencoded",
4674
- Accept: accept
4675
- },
4676
- method: "POST",
4677
- body: new URLSearchParams({ query: query3 })
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
- if (!res.ok) {
4685
- const body = await res.text();
4686
- throw new Error(`QLever query failed (HTTP ${res.status}): ${body}`);
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 await res.json();
6189
+ return result;
4689
6190
  }
4690
- async subset(query3, accept = "text/turtle") {
4691
- const res = await fetch(this.queryEndpoint, {
4692
- headers: {
4693
- ...this.baseHeaders,
4694
- "Content-Type": "application/x-www-form-urlencoded",
4695
- Accept: accept
4696
- },
4697
- method: "POST",
4698
- body: new URLSearchParams({ query: query3 })
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
- return await res.text();
6222
+ this._entityDetails.set(detailsUpdate);
6223
+ return result;
4701
6224
  }
4702
- async update(update) {
4703
- return _QLever._retryOnLocked(async () => {
4704
- const res = await fetch(this.updateEndpoint, {
4705
- headers: {
4706
- ...this.baseHeaders,
4707
- "Content-Type": "application/x-www-form-urlencoded"
4708
- },
4709
- method: "POST",
4710
- body: new URLSearchParams({ update })
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
- if (!res.ok) {
4713
- const body = await res.text();
4714
- if (res.status === 423)
4715
- throw new QLeverLockedError(body);
4716
- throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
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
- return await res.json();
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
- * Insert quads via the /data endpoint, grouped by named graph.
4723
- * This is preferred over SPARQL INSERT DATA for QLever because
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
- async insertData(quads) {
4727
- await this._postToDataEndpoint(quads, this.dataEndpoint);
6371
+ reset() {
6372
+ this._documentInfoMap.set({});
6373
+ this._projectDocumentsData.set({
6374
+ duplicateCount: 0,
6375
+ documentsBySuffix: {},
6376
+ documentsByContentCategory: {}
6377
+ });
4728
6378
  }
4729
6379
  /**
4730
- * Delete quads via the /data/delete endpoint, grouped by named graph.
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
- async deleteData(quads) {
4733
- await this._postToDataEndpoint(quads, `${this.dataEndpoint}/delete`);
4734
- }
4735
- async _postToDataEndpoint(quads, baseUrl) {
4736
- const nquads = await this._quadsToNQuads(quads);
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
- _quadsToNQuads(quads) {
4757
- return new Promise((resolve2, reject) => {
4758
- const writer = new import_n3.Writer({ format: "application/n-quads" });
4759
- writer.addQuads(quads);
4760
- writer.end((err, result) => err ? reject(err) : resolve2(result));
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
- // libs/js/databases/src/lib/graph/main.ts
4766
- var CueGraphDatabase = class {
4767
- constructor(options) {
4768
- this.options = options;
4769
- switch (this.options.graphType) {
4770
- case "qlever":
4771
- this._db = new QLever(this.options);
4772
- break;
4773
- case "fuseki":
4774
- this._db = new Fuseki(this.options);
4775
- break;
4776
- default:
4777
- throw new Error(`Unsupported graph type: ${this.options.graphType}`);
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
- _db;
4781
- ping() {
4782
- return this._db.ping();
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
- query(queryString, accept) {
4785
- return this._db.query(queryString, accept);
6516
+ async _fetchDocumentsByContentCategory() {
6517
+ return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
4786
6518
  }
4787
- subset(queryString, accept) {
4788
- if (this.options.graphType === "qlever") {
4789
- if (accept && accept !== "text/turtle") {
4790
- return Promise.reject(
4791
- new Error(
4792
- `QLever only supports 'text/turtle' for CONSTRUCT/DESCRIBE queries.`
4793
- )
4794
- );
4795
- }
4796
- return this._db.subset(
4797
- queryString,
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
- update(updateString) {
4804
- return this._db.update(updateString);
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
- /** Returns true if this backend supports the /data bulk-insert endpoint (QLever only). */
4807
- supportsDataEndpoint() {
4808
- return this.options.graphType === "qlever";
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
- * Insert quads using the backend's preferred bulk-insert mechanism.
4812
- * For QLever: uses the /data endpoint (correctly registers named graphs).
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
- insertData(quads) {
4816
- if (this.options.graphType === "qlever") {
4817
- return this._db.insertData(quads);
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
- * Delete quads using the backend's preferred bulk-delete mechanism.
4823
- * For QLever: uses the /data/delete endpoint.
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
- deleteData(quads) {
4827
- if (this.options.graphType === "qlever") {
4828
- return this._db.deleteData(quads);
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
- // libs/js/databases/src/lib/blob/blob.ts
4835
- var import_storage3 = require("firebase/storage");
4836
- var CueBlobStorage = class {
4837
- constructor(options) {
4838
- this.options = options;
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
- * Upload binary data to the raw storage bucket with retry logic.
6660
+ * Lazily batch-fetch document info for the given UUIDs.
6661
+ * Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
4842
6662
  */
4843
- async uploadRaw(blobName, data, metadata, maxRetries = 3) {
4844
- const fileRef = (0, import_storage3.ref)(this.options.storageRaw, blobName);
4845
- let attempt = 0;
4846
- let lastError;
4847
- while (attempt < maxRetries) {
4848
- try {
4849
- await new Promise((resolve2, reject) => {
4850
- const task = (0, import_storage3.uploadBytesResumable)(fileRef, data, {
4851
- customMetadata: metadata
4852
- });
4853
- task.on("state_changed", null, reject, resolve2);
4854
- });
4855
- return;
4856
- } catch (err) {
4857
- lastError = err;
4858
- attempt++;
4859
- if (attempt < maxRetries) {
4860
- await new Promise((res) => setTimeout(res, 1e3 * attempt));
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
- * Upload data to the processed storage bucket.
4868
- * Skips upload and returns `false` if the blob already exists.
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
- async uploadProcessed(blobName, data, metadata) {
4871
- const fileRef = (0, import_storage3.ref)(this.options.storageProcessed, blobName);
4872
- const existing = await (0, import_storage3.getMetadata)(fileRef).catch(() => null);
4873
- if (existing)
4874
- return false;
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
- * List all blob names directly under `prefix` in the raw storage bucket.
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
- async listRaw(prefix) {
4882
- const listRef = (0, import_storage3.ref)(this.options.storageRaw, prefix);
4883
- const result = await (0, import_storage3.listAll)(listRef);
4884
- return result.items.map((item) => item.name);
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
- * Download a file from the public bucket and return its contents as a string.
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
- async downloadPublic(blobName) {
4890
- const fileRef = (0, import_storage3.ref)(this.options.storagePublic, blobName);
4891
- const controller = new AbortController();
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/cue-sdk/src/lib/sync.ts
4913
- var import_promises3 = require("fs/promises");
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
- const wasmDir = (0, import_path3.join)(__dirname, "assets", "wasm");
5905
- const wasmBinary = await (0, import_promises3.readFile)((0, import_path3.join)(wasmDir, "dir_scanner_wasm_bg.wasm"));
5906
- const glueUrl = (0, import_url.pathToFileURL)((0, import_path3.join)(wasmDir, "dir_scanner_wasm.mjs")).href;
5907
- const mod = await import(
5908
- /* @vite-ignore */
5909
- glueUrl
5910
- );
5911
- await mod.default({ module_or_path: wasmBinary });
5912
- _scanFn = mod.scan;
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
- return (0, import_path3.join)((0, import_os.tmpdir)(), `cue-sync-pending-${spaceId}.json`);
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 (0, import_promises3.readFile)(_pendingFilePath(spaceId), "utf-8");
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 (0, import_promises3.writeFile)(_pendingFilePath(batch.spaceId), data, "utf-8");
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 (0, import_promises3.unlink)(_pendingFilePath(spaceId));
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
- * Flushes any pending metadata items from a previous interrupted sync.
5973
- * Safe to call even when there is nothing new to upload.
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 flushPendingMetadata(spaceId, verbose) {
5976
- const token = await this._auth.getToken();
5977
- if (!token)
5978
- throw new Error("Not authenticated. Call cue.auth.signIn() first.");
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, token, verbose);
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, tier) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance")),
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, tier) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
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, token, verbose);
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 = await (0, import_promises3.readFile)(file.fullPath);
7943
+ const fileBuffer = file.data ?? new Uint8Array(await _readNodeFile(file.fullPath));
6094
7944
  await this._blob.uploadRaw(
6095
7945
  rawMeta.blob_name,
6096
- new Uint8Array(fileBuffer),
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, tier) ?? Promise.resolve({ creditsAvailable: 0 }));
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, token, verbose) {
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, token, verbose);
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
- const token = await this._auth.getToken();
6262
- if (!token)
8123
+ if (!this._auth.currentUser)
6263
8124
  return;
6264
- await this._flushBatch(this._pendingItems, this._pendingSpaceId, token, verbose);
8125
+ await this._flushBatch(this._pendingItems, this._pendingSpaceId, verbose);
6265
8126
  }
6266
- async _flushBatch(items, spaceId, token, verbose) {
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, token);
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, token) {
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 fetch(`${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`, {
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: new Uint8Array(await (0, import_promises3.readFile)(f.fullPath))
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 BUCKET_RAW2 = "spaces_raw_eu_west6";
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 storageRaw = (0, import_storage4.getStorage)(this._app, BUCKET_RAW2);
6394
- const storageProcessed = (0, import_storage4.getStorage)(this._app, BUCKET_PROCESSED2);
6395
- const storagePublic = (0, import_storage4.getStorage)(this._app, BUCKET_PUBLIC2);
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, import_storage4.connectStorageEmulator)(storageRaw, storageHost, storagePort);
6400
- (0, import_storage4.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
6401
- (0, import_storage4.connectStorageEmulator)(storagePublic, storageHost, storagePort);
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({ storageRaw, storageProcessed, storagePublic });
6404
- const syncApi = new CueSyncApi(this.auth, projects, blob, this._endpoints.gatewayUrl);
6405
- const api = new CueApi(this.auth, this._endpoints.gatewayUrl, projects, syncApi);
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 (query3) => queryHandler(query3, space, emulators);
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 import_storage5 = require("firebase/storage");
6547
- var import_path4 = require("path");
6548
- var import_promises4 = require("fs/promises");
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, import_storage5.listAll)((0, import_storage5.ref)(storage, subDir));
6554
- const outputDir = (0, import_path4.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
6555
- await (0, import_promises4.mkdir)(outputDir, { recursive: true });
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, import_storage5.getBytes)(fileRef);
6561
- const outputPath = (0, import_path4.join)(outputDir, fileRef.name);
6562
- await (0, import_promises4.writeFile)(outputPath, Buffer.from(bytes));
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 import_fs2 = require("fs");
8711
+ var import_fs = require("fs");
6588
8712
  var import_stream = require("stream");
6589
- var import_fs3 = require("fs");
6590
- var import_zlib2 = require("zlib");
6591
- var import_promises5 = require("stream/promises");
6592
- var import_promises6 = require("fs/promises");
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 import_fs4 = require("fs");
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, import_fs4.existsSync)(filePath) || (0, import_fs4.existsSync)(`${filePath}.gz`)) {
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, import_fs2.createWriteStream)(filePath, { flags: "w" });
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, import_promises5.pipeline)(nodeStream, fileStream);
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, import_fs2.createWriteStream)(filePath, { flags: "w" });
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, import_zlib2.createGzip)();
6771
- const source = (0, import_fs3.createReadStream)(filePath);
6772
- const dest = (0, import_fs2.createWriteStream)(gzFilePath);
6773
- (0, import_promises5.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
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, import_promises6.unlink)(filePath);
8899
+ await (0, import_promises3.unlink)(filePath);
6776
8900
  }
6777
- async function _doQuery(query3, spaceId, url) {
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: query3
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 import_fs5 = require("fs");
8991
+ var import_fs4 = require("fs");
6868
8992
  async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
6869
- const stream = (0, import_fs5.createReadStream)(filePath);
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: query3, load } = options;
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 (query3) {
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 import_storage6 = require("firebase/storage");
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, import_storage6.listAll)((0, import_storage6.ref)(storage, `${space}/triples`));
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, import_storage6.getStream)(fileRef);
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, import_storage6.getMetadata)(fileRef);
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, import_storage6.uploadBytesResumable)((0, import_storage6.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
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 import_promises7 = require("fs/promises");
7021
- var import_fs6 = require("fs");
7022
- var import_path5 = require("path");
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, import_path5.resolve)(path);
7052
- const pathStat = await (0, import_promises7.stat)(resolvedPath);
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, import_fs6.createReadStream)(resolvedPath));
7059
- const relativePath = (0, import_path5.basename)(resolvedPath);
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 import_fs7 = require("fs");
7183
- var import_zlib3 = require("zlib");
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, import_fs7.createReadStream)(input);
7231
- const inputStream = isGzipped ? fileStream.pipe((0, import_zlib3.createGunzip)()) : fileStream;
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, import_fs7.createWriteStream)(output);
9366
+ const writeStream = (0, import_fs6.createWriteStream)(output);
7240
9367
  if (isGzipped) {
7241
- const gzip = (0, import_zlib3.createGzip)();
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 import_fs8 = require("fs");
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, import_fs8.readFileSync)(file1, "utf8");
7336
- const content2 = (0, import_fs8.readFileSync)(file2, "utf8");
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, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "package.json"), "utf8"));
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, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf8"));
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);