@qaecy/cue-cli 0.0.35 → 0.0.37

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 +2945 -839
  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,10 +308,73 @@ var init_worker_pool = __esm({
91
308
  }
92
309
  });
93
310
 
311
+ // libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts
312
+ var md5_builder_node_exports = {};
313
+ __export(md5_builder_node_exports, {
314
+ md5FromWorker: () => md5FromWorker,
315
+ md5NoWorker: () => md5NoWorker
316
+ });
317
+ async function md5FromWorker(filePaths, hashWorkerPath, verbose = false, logIntervalPct = 1) {
318
+ const { WorkerPool: WorkerPool2 } = await Promise.resolve().then(() => (init_worker_pool(), worker_pool_exports));
319
+ const pool = new WorkerPool2(hashWorkerPath);
320
+ const hashes = filePaths.map((filePath) => pool.hashFile(filePath));
321
+ if (verbose) {
322
+ let completed = 0;
323
+ let lastPct = 0;
324
+ hashes.forEach(
325
+ (promise) => promise.then(() => {
326
+ completed++;
327
+ const pct = Math.floor(completed / filePaths.length * 100);
328
+ if (pct - lastPct >= logIntervalPct) {
329
+ lastPct = pct;
330
+ console.info(`MD5 progress: ${completed}/${filePaths.length} (${pct}%)`);
331
+ }
332
+ })
333
+ );
334
+ }
335
+ const ret = await Promise.all(hashes);
336
+ await pool.close();
337
+ return ret;
338
+ }
339
+ async function md5NoWorker(filePaths, verbose) {
340
+ if (verbose)
341
+ console.info(`Calculating MD5 hashes for ${filePaths.length} files...`);
342
+ const concurrency = 50;
343
+ const hashes = [];
344
+ for (let i = 0; i < filePaths.length; i += concurrency) {
345
+ const chunk = filePaths.slice(i, i + concurrency);
346
+ const chunkHashes = await Promise.all(
347
+ chunk.map(async (f) => {
348
+ const { createReadStream: createReadStream5 } = await import("fs");
349
+ const { fromReadStream: fromReadStream2 } = await Promise.resolve().then(() => (init_src(), src_exports));
350
+ const stream = createReadStream5(f);
351
+ return fromReadStream2(stream);
352
+ })
353
+ );
354
+ hashes.push(...chunkHashes);
355
+ if (verbose) {
356
+ const pct = Math.min(
357
+ 100,
358
+ Math.round((i + chunk.length) / filePaths.length * 100)
359
+ );
360
+ console.info(
361
+ `MD5 progress: ${pct}% (${i + chunk.length}/${filePaths.length})`
362
+ );
363
+ }
364
+ }
365
+ if (verbose)
366
+ console.info(`Calculated MD5 hashes for ${filePaths.length} files.`);
367
+ return hashes;
368
+ }
369
+ var init_md5_builder_node = __esm({
370
+ "libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts"() {
371
+ }
372
+ });
373
+
94
374
  // apps/desktop/cue-cli/src/main.ts
95
375
  var import_commander = require("commander");
96
- var 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");
@@ -237,63 +517,763 @@ var COLLECTION_USER_TERMS_ACCEPT = "userTermsAcceptance";
237
517
  var COLLECTION_TIERS = "tiers";
238
518
 
239
519
  // libs/js/firebase/src/lib/firebase.ts
240
- var import_storage = require("firebase/storage");
241
- var import_firestore = require("firebase/firestore");
520
+ var import_storage2 = require("firebase/storage");
521
+ var import_firestore2 = require("firebase/firestore");
242
522
  var import_auth = require("firebase/auth");
243
523
  var import_app = require("firebase/app");
244
- var CueFirebase = class _CueFirebase {
245
- static _instance;
246
- _muted = true;
247
- _emulator = false;
248
- constructor(config, auth, muted = false) {
249
- this._muted = muted;
250
- this._init(config, auth);
251
- }
252
- static getInstance(config, auth, muted = false) {
253
- if (!_CueFirebase._instance) {
254
- if (config === void 0)
255
- throw new Error("Config needed for instantiation!");
256
- if (!muted)
257
- console.info("Creating new CueFirebase instance");
258
- _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;
259
545
  }
260
- return _CueFirebase._instance;
261
546
  }
262
- _functionAcceptTerms;
263
- _functionEmitMessage;
264
- _functionGetUserInfo;
265
- _functionChangeUserRoleOnProject;
266
- _functionInviteUserToProject;
267
- _functionRemoveUserFromProject;
268
- _storageProcessed;
269
- _storageRaw;
270
- _storageLogs;
271
- _storageChatSessions;
272
- _storagePublic;
273
- _storagePersistence;
274
- _collectionChatSessions;
275
- _collectionOrganizations;
276
- _collectionProjects;
277
- _collectionRDFWriting;
278
- _collectionAPIKeys;
279
- _collectionUsers;
280
- _collectionUserTermsAcceptance;
281
- _collectionTiers;
282
- _auth;
283
- _app;
284
- get functionAcceptTerms() {
285
- return this._functionAcceptTerms;
547
+ async ping() {
548
+ const query4 = "ASK { }";
549
+ const res = await this.query(query4);
550
+ return res.boolean;
286
551
  }
287
- get functionEmitMessage() {
288
- 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();
289
574
  }
290
- get functionGetUserInfo() {
291
- 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();
292
590
  }
293
- get functionInviteUserToProject() {
294
- 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
+ };
295
613
  }
296
- get functionChangeUserRoleOnProject() {
614
+ };
615
+
616
+ // libs/js/databases/src/lib/graph/qlever.ts
617
+ var import_n3 = require("n3");
618
+ var QLeverLockedError = class extends Error {
619
+ constructor(body) {
620
+ super(`QLever is locked (rebuild in progress): ${body}`);
621
+ this.name = "QLeverLockedError";
622
+ }
623
+ };
624
+ var QLever = class _QLever {
625
+ queryEndpoint;
626
+ updateEndpoint;
627
+ dataEndpoint;
628
+ baseHeaders;
629
+ static RELEVANT_HEADER_KEYS = [
630
+ "authorization",
631
+ "Authorization",
632
+ "x-project-id",
633
+ "x-user-roles"
634
+ // add more if needed
635
+ ];
636
+ /** Max retries on 423 Locked (rebuild in progress). */
637
+ static LOCKED_MAX_RETRIES = parseInt(
638
+ (typeof process !== "undefined" ? process.env["QLEVER_LOCKED_MAX_RETRIES"] : void 0) ?? "10",
639
+ 10
640
+ );
641
+ /** Base delay (ms) for exponential backoff on 423. */
642
+ static LOCKED_BASE_DELAY_MS = parseInt(
643
+ (typeof process !== "undefined" ? process.env["QLEVER_LOCKED_BASE_DELAY_MS"] : void 0) ?? "2000",
644
+ 10
645
+ );
646
+ /**
647
+ * Retry an async write operation on 423 Locked with exponential backoff + jitter.
648
+ * 423 means qlever accessor has an ongoing rebuild; we should wait and retry.
649
+ */
650
+ static async _retryOnLocked(fn) {
651
+ const maxRetries = _QLever.LOCKED_MAX_RETRIES;
652
+ const baseDelayMs = _QLever.LOCKED_BASE_DELAY_MS;
653
+ let lastError;
654
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
655
+ try {
656
+ return await fn();
657
+ } catch (err) {
658
+ lastError = err;
659
+ if (err instanceof QLeverLockedError && attempt < maxRetries) {
660
+ const jitter = 0.5 + Math.random();
661
+ const delay = baseDelayMs * Math.pow(2, attempt) * jitter;
662
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
663
+ continue;
664
+ }
665
+ throw err;
666
+ }
667
+ }
668
+ throw lastError;
669
+ }
670
+ constructor(graphOptions) {
671
+ this.queryEndpoint = graphOptions.queryEndpoint;
672
+ this.updateEndpoint = graphOptions.updateEndpoint;
673
+ this.dataEndpoint = this.updateEndpoint.replace(/\/update$/, "/data");
674
+ this.baseHeaders = Object.fromEntries(
675
+ Object.entries(graphOptions.originalHeaders || {}).filter(
676
+ ([key]) => _QLever.RELEVANT_HEADER_KEYS.includes(key)
677
+ )
678
+ );
679
+ this.baseHeaders["x-user-roles"] = "admin";
680
+ }
681
+ async ping() {
682
+ const query4 = "ASK { }";
683
+ const res = await this.query(query4);
684
+ return res.boolean;
685
+ }
686
+ async query(query4, accept = "application/sparql-results+json") {
687
+ let res;
688
+ try {
689
+ res = await fetch(this.queryEndpoint, {
690
+ headers: {
691
+ ...this.baseHeaders,
692
+ "Content-Type": "application/x-www-form-urlencoded",
693
+ Accept: accept
694
+ },
695
+ method: "POST",
696
+ body: new URLSearchParams({ query: query4 })
697
+ });
698
+ } catch (err) {
699
+ throw new Error(
700
+ `QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
701
+ );
702
+ }
703
+ if (!res.ok) {
704
+ const body = await res.text();
705
+ throw new Error(`QLever query failed (HTTP ${res.status}): ${body}`);
706
+ }
707
+ return await res.json();
708
+ }
709
+ async subset(query4, accept = "text/turtle") {
710
+ const res = await fetch(this.queryEndpoint, {
711
+ headers: {
712
+ ...this.baseHeaders,
713
+ "Content-Type": "application/x-www-form-urlencoded",
714
+ Accept: accept
715
+ },
716
+ method: "POST",
717
+ body: new URLSearchParams({ query: query4 })
718
+ });
719
+ return await res.text();
720
+ }
721
+ async update(update) {
722
+ return _QLever._retryOnLocked(async () => {
723
+ const res = await fetch(this.updateEndpoint, {
724
+ headers: {
725
+ ...this.baseHeaders,
726
+ "Content-Type": "application/x-www-form-urlencoded"
727
+ },
728
+ method: "POST",
729
+ body: new URLSearchParams({ update })
730
+ });
731
+ if (!res.ok) {
732
+ const body = await res.text();
733
+ if (res.status === 423)
734
+ throw new QLeverLockedError(body);
735
+ throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
736
+ }
737
+ return await res.json();
738
+ });
739
+ }
740
+ /**
741
+ * Insert quads via the /data endpoint, grouped by named graph.
742
+ * This is preferred over SPARQL INSERT DATA for QLever because
743
+ * the /data endpoint correctly registers named graphs in the index.
744
+ */
745
+ async insertData(quads) {
746
+ await this._postToDataEndpoint(quads, this.dataEndpoint);
747
+ }
748
+ /**
749
+ * Delete quads via the /data/delete endpoint, grouped by named graph.
750
+ */
751
+ async deleteData(quads) {
752
+ await this._postToDataEndpoint(quads, `${this.dataEndpoint}/delete`);
753
+ }
754
+ async _postToDataEndpoint(quads, baseUrl) {
755
+ const nquads = await this._quadsToNQuads(quads);
756
+ const body = await _gzip(Buffer.from(nquads, "utf-8"));
757
+ await _QLever._retryOnLocked(async () => {
758
+ const res = await fetch(baseUrl, {
759
+ method: "POST",
760
+ headers: {
761
+ ...this.baseHeaders,
762
+ "Content-Type": "application/n-quads",
763
+ "Content-Encoding": "gzip"
764
+ },
765
+ body
766
+ });
767
+ if (!res.ok) {
768
+ const text = await res.text();
769
+ if (res.status === 423)
770
+ throw new QLeverLockedError(text);
771
+ throw new Error(`QLever data POST failed (HTTP ${res.status}): ${text}`);
772
+ }
773
+ });
774
+ }
775
+ _quadsToNQuads(quads) {
776
+ return new Promise((resolve2, reject) => {
777
+ const writer = new import_n3.Writer({ format: "application/n-quads" });
778
+ writer.addQuads(quads);
779
+ writer.end((err, result) => err ? reject(err) : resolve2(result));
780
+ });
781
+ }
782
+ };
783
+ async function _gzip(input) {
784
+ const cs = new CompressionStream("gzip");
785
+ const writer = cs.writable.getWriter();
786
+ writer.write(new Uint8Array(input));
787
+ writer.close();
788
+ const chunks = [];
789
+ const reader = cs.readable.getReader();
790
+ while (true) {
791
+ const { done, value } = await reader.read();
792
+ if (done)
793
+ break;
794
+ chunks.push(value);
795
+ }
796
+ const total = chunks.reduce((n, c) => n + c.byteLength, 0);
797
+ const out = new Uint8Array(total);
798
+ let offset = 0;
799
+ for (const chunk of chunks) {
800
+ out.set(chunk, offset);
801
+ offset += chunk.byteLength;
802
+ }
803
+ return out.buffer;
804
+ }
805
+
806
+ // libs/js/databases/src/lib/graph/main.ts
807
+ var CueGraphDatabase = class {
808
+ constructor(options) {
809
+ this.options = options;
810
+ switch (this.options.graphType) {
811
+ case "qlever":
812
+ this._db = new QLever(this.options);
813
+ break;
814
+ case "fuseki":
815
+ this._db = new Fuseki(this.options);
816
+ break;
817
+ default:
818
+ throw new Error(`Unsupported graph type: ${this.options.graphType}`);
819
+ }
820
+ }
821
+ _db;
822
+ ping() {
823
+ return this._db.ping();
824
+ }
825
+ query(queryString, accept) {
826
+ return this._db.query(queryString, accept);
827
+ }
828
+ subset(queryString, accept) {
829
+ if (this.options.graphType === "qlever") {
830
+ if (accept && accept !== "text/turtle") {
831
+ return Promise.reject(
832
+ new Error(
833
+ `QLever only supports 'text/turtle' for CONSTRUCT/DESCRIBE queries.`
834
+ )
835
+ );
836
+ }
837
+ return this._db.subset(
838
+ queryString,
839
+ accept
840
+ );
841
+ }
842
+ return this._db.subset(queryString, accept);
843
+ }
844
+ update(updateString) {
845
+ return this._db.update(updateString);
846
+ }
847
+ /** Returns true if this backend supports the /data bulk-insert endpoint (QLever only). */
848
+ supportsDataEndpoint() {
849
+ return this.options.graphType === "qlever";
850
+ }
851
+ /**
852
+ * Insert quads using the backend's preferred bulk-insert mechanism.
853
+ * For QLever: uses the /data endpoint (correctly registers named graphs).
854
+ * For Fuseki: falls back to SPARQL INSERT DATA.
855
+ */
856
+ insertData(quads) {
857
+ if (this.options.graphType === "qlever") {
858
+ return this._db.insertData(quads);
859
+ }
860
+ return Promise.reject(new Error("insertData not supported for Fuseki \u2014 use update() with SPARQL INSERT DATA"));
861
+ }
862
+ /**
863
+ * Delete quads using the backend's preferred bulk-delete mechanism.
864
+ * For QLever: uses the /data/delete endpoint.
865
+ * For Fuseki: falls back to SPARQL DELETE DATA.
866
+ */
867
+ deleteData(quads) {
868
+ if (this.options.graphType === "qlever") {
869
+ return this._db.deleteData(quads);
870
+ }
871
+ return Promise.reject(new Error("deleteData not supported for Fuseki \u2014 use update() with SPARQL DELETE DATA"));
872
+ }
873
+ };
874
+
875
+ // libs/js/databases/src/lib/blob/blob.ts
876
+ var import_storage = require("firebase/storage");
877
+ var CueBlobStorage = class {
878
+ constructor(options) {
879
+ this.options = options;
880
+ }
881
+ /** Paths known to be absent, keyed as `bucket:path`. Avoids repeat 404 requests. */
882
+ _knownMissing = /* @__PURE__ */ new Set();
883
+ // ─── Storage bucket resolution ────────────────────────────────────────────
884
+ _bucket(name) {
885
+ switch (name) {
886
+ case "raw":
887
+ return this.options.storageRaw;
888
+ case "processed":
889
+ return this.options.storageProcessed;
890
+ case "logs":
891
+ return this.options.storageLogs;
892
+ case "chatSessions":
893
+ return this.options.storageChatSessions;
894
+ case "public":
895
+ return this.options.storagePublic;
896
+ case "persistence":
897
+ return this.options.storagePersistence;
898
+ }
899
+ }
900
+ // ─── Downloads ────────────────────────────────────────────────────────────
901
+ /** Get an authenticated download URL. Returns undefined if the file does not exist. */
902
+ async getDownloadURL(bucket, path) {
903
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
904
+ try {
905
+ return await (0, import_storage.getDownloadURL)(fileRef);
906
+ } catch (err) {
907
+ if (err?.code === "storage/object-not-found")
908
+ return void 0;
909
+ throw err;
910
+ }
911
+ }
912
+ /**
913
+ * Download URL with a `?t=<updated>` suffix, bypassing caches.
914
+ * Returns undefined if the file does not exist.
915
+ */
916
+ async getCacheBustedUrl(bucket, path) {
917
+ const key = `${bucket}:${path}`;
918
+ if (this._knownMissing.has(key))
919
+ return void 0;
920
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
921
+ try {
922
+ const metadata = await (0, import_storage.getMetadata)(fileRef);
923
+ const url = await (0, import_storage.getDownloadURL)(fileRef);
924
+ return `${url}&t=${encodeURIComponent(metadata.updated)}`;
925
+ } catch (err) {
926
+ if (err?.code === "storage/object-not-found" || err?.status === 404) {
927
+ this._knownMissing.add(key);
928
+ console.debug(`[CueBlobStorage] ${path} not found (404 OK \u2014 optional cache file)`);
929
+ return void 0;
930
+ }
931
+ throw err;
932
+ }
933
+ }
934
+ /** Download a file as a Blob. Returns undefined if the file does not exist. */
935
+ async getFile(bucket, path) {
936
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
937
+ try {
938
+ return await (0, import_storage.getBlob)(fileRef);
939
+ } catch (err) {
940
+ if (err?.code === "storage/object-not-found")
941
+ return void 0;
942
+ throw err;
943
+ }
944
+ }
945
+ /** Returns true if the file exists. */
946
+ async fileExists(bucket, path) {
947
+ return await this.getDownloadURL(bucket, path) !== void 0;
948
+ }
949
+ /**
950
+ * Download a file from the public bucket and return its contents as a string.
951
+ * Uses a cache-busted URL to ensure the latest version is fetched.
952
+ */
953
+ async downloadPublic(blobName) {
954
+ const fileRef = (0, import_storage.ref)(this.options.storagePublic, blobName);
955
+ const controller = new AbortController();
956
+ const timeout = setTimeout(() => controller.abort(), 1e4);
957
+ const raceAbort = (p) => Promise.race([
958
+ p,
959
+ new Promise((_, reject) => {
960
+ controller.signal.addEventListener(
961
+ "abort",
962
+ () => reject(new DOMException(`Download timed out: ${blobName}`, "AbortError"))
963
+ );
964
+ })
965
+ ]);
966
+ try {
967
+ const [url, metadata] = await Promise.all([
968
+ raceAbort((0, import_storage.getDownloadURL)(fileRef)),
969
+ raceAbort((0, import_storage.getMetadata)(fileRef))
970
+ ]);
971
+ const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
972
+ const res = await fetch(cacheBustedUrl, { signal: controller.signal });
973
+ if (!res.ok)
974
+ throw new Error(`HTTP ${res.status}`);
975
+ return res.text();
976
+ } catch (err) {
977
+ const isTimeout = err instanceof Error && err.name === "AbortError";
978
+ throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
979
+ } finally {
980
+ clearTimeout(timeout);
981
+ }
982
+ }
983
+ // ─── Metadata ─────────────────────────────────────────────────────────────
984
+ /** Read file metadata. Returns undefined if the file does not exist. */
985
+ async getMetadata(bucket, path) {
986
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
987
+ try {
988
+ const m = await (0, import_storage.getMetadata)(fileRef);
989
+ return {
990
+ updated: m.updated,
991
+ contentType: m.contentType,
992
+ size: m.size,
993
+ customMetadata: m.customMetadata
994
+ };
995
+ } catch (err) {
996
+ if (err?.code === "storage/object-not-found")
997
+ return void 0;
998
+ throw err;
999
+ }
1000
+ }
1001
+ /** Update custom metadata on an existing file. */
1002
+ async setMetadata(bucket, path, customMetadata) {
1003
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
1004
+ await (0, import_storage.updateMetadata)(fileRef, { customMetadata });
1005
+ }
1006
+ // ─── Uploads ──────────────────────────────────────────────────────────────
1007
+ /**
1008
+ * Resumable upload. Returns an {@link UploadHandle} with progress callbacks
1009
+ * and pause/resume/cancel controls. Use for large files or files where
1010
+ * upload progress needs to be surfaced in the UI.
1011
+ */
1012
+ uploadResumable(bucket, path, data, customMetadata) {
1013
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
1014
+ const task = (0, import_storage.uploadBytesResumable)(fileRef, data, customMetadata ? { customMetadata } : void 0);
1015
+ return {
1016
+ complete: () => new Promise((resolve2, reject) => task.then(() => resolve2(), reject)),
1017
+ pause: () => task.pause(),
1018
+ resume: () => task.resume(),
1019
+ cancel: () => task.cancel(),
1020
+ onProgress: (cb) => {
1021
+ const unsub = task.on("state_changed", (snap) => {
1022
+ cb(snap.bytesTransferred, snap.totalBytes);
1023
+ });
1024
+ return () => unsub();
1025
+ }
1026
+ };
1027
+ }
1028
+ /**
1029
+ * Simple one-shot upload. Waits for completion before resolving.
1030
+ * Use for small files where progress feedback is not needed.
1031
+ */
1032
+ async uploadBytes(bucket, path, data, customMetadata) {
1033
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
1034
+ await (0, import_storage.uploadBytes)(fileRef, data, customMetadata ? { customMetadata } : void 0);
1035
+ }
1036
+ /** Upload a string or base64-encoded value. */
1037
+ async uploadString(bucket, path, data, format = import_storage.StringFormat.RAW, customMetadata) {
1038
+ const fileRef = (0, import_storage.ref)(this._bucket(bucket), path);
1039
+ await (0, import_storage.uploadString)(fileRef, data, format, customMetadata ? { customMetadata } : void 0);
1040
+ }
1041
+ // ─── Listing ──────────────────────────────────────────────────────────────
1042
+ /** List all item names directly under `prefix` in the given bucket. */
1043
+ async listFiles(bucket, prefix) {
1044
+ const listRef = (0, import_storage.ref)(this._bucket(bucket), prefix);
1045
+ const result = await (0, import_storage.listAll)(listRef);
1046
+ return result.items.map((item) => item.name);
1047
+ }
1048
+ /**
1049
+ * Recursively list all file full-paths under `prefix` in the given bucket.
1050
+ * Returns full storage paths (not just names).
1051
+ */
1052
+ async listFilesRecursive(bucket, prefix) {
1053
+ const storage = this._bucket(bucket);
1054
+ const collectFiles = async (path) => {
1055
+ const listResult = await (0, import_storage.listAll)((0, import_storage.ref)(storage, path));
1056
+ let files = listResult.items.map((item) => item.fullPath);
1057
+ for (const folder of listResult.prefixes) {
1058
+ files = files.concat(await collectFiles(folder.fullPath));
1059
+ }
1060
+ return files;
1061
+ };
1062
+ return collectFiles(prefix);
1063
+ }
1064
+ /**
1065
+ * Delete all files under `prefix` recursively.
1066
+ * Returns the number of files deleted.
1067
+ */
1068
+ async deleteDirectory(bucket, prefix) {
1069
+ const { deleteObject } = await import("firebase/storage");
1070
+ const storage = this._bucket(bucket);
1071
+ let deletedCount = 0;
1072
+ const deleteAll = async (path) => {
1073
+ const listResult = await (0, import_storage.listAll)((0, import_storage.ref)(storage, path));
1074
+ for (const item of listResult.items) {
1075
+ await deleteObject(item);
1076
+ deletedCount++;
1077
+ }
1078
+ for (const folder of listResult.prefixes) {
1079
+ await deleteAll(folder.fullPath);
1080
+ }
1081
+ };
1082
+ await deleteAll(prefix);
1083
+ return deletedCount;
1084
+ }
1085
+ // ─── Legacy helpers kept for CueBlobStorage consumers ────────────────────
1086
+ // (used by loaders / processors that were already depending on CueBlobStorage)
1087
+ /** Upload binary data to the raw bucket with retry logic. */
1088
+ async uploadRaw(blobName, data, metadata, maxRetries = 3, signal, onProgress) {
1089
+ const fileRef = (0, import_storage.ref)(this.options.storageRaw, blobName);
1090
+ let attempt = 0;
1091
+ let lastError;
1092
+ while (attempt < maxRetries) {
1093
+ try {
1094
+ await new Promise((resolve2, reject) => {
1095
+ if (signal?.aborted) {
1096
+ reject(new DOMException("Upload cancelled", "AbortError"));
1097
+ return;
1098
+ }
1099
+ const task = (0, import_storage.uploadBytesResumable)(fileRef, data, { customMetadata: metadata });
1100
+ const onAbort = () => {
1101
+ task.cancel();
1102
+ reject(new DOMException("Upload cancelled", "AbortError"));
1103
+ };
1104
+ signal?.addEventListener("abort", onAbort, { once: true });
1105
+ task.on(
1106
+ "state_changed",
1107
+ (snapshot) => {
1108
+ if (onProgress) {
1109
+ const pct = Math.round(snapshot.bytesTransferred / snapshot.totalBytes * 100);
1110
+ onProgress(pct);
1111
+ }
1112
+ },
1113
+ (error) => {
1114
+ signal?.removeEventListener("abort", onAbort);
1115
+ reject(error);
1116
+ },
1117
+ () => {
1118
+ signal?.removeEventListener("abort", onAbort);
1119
+ resolve2();
1120
+ }
1121
+ );
1122
+ });
1123
+ return;
1124
+ } catch (err) {
1125
+ if (signal?.aborted || err instanceof DOMException && err.name === "AbortError") {
1126
+ throw err;
1127
+ }
1128
+ lastError = err;
1129
+ attempt++;
1130
+ if (attempt < maxRetries) {
1131
+ await new Promise((res) => setTimeout(res, 1e3 * attempt));
1132
+ }
1133
+ }
1134
+ }
1135
+ throw lastError;
1136
+ }
1137
+ /**
1138
+ * Upload data to the processed bucket.
1139
+ * Skips upload and returns `false` if the blob already exists.
1140
+ */
1141
+ async uploadProcessed(blobName, data, metadata) {
1142
+ const fileRef = (0, import_storage.ref)(this.options.storageProcessed, blobName);
1143
+ const existing = await (0, import_storage.getMetadata)(fileRef).catch(() => null);
1144
+ if (existing)
1145
+ return false;
1146
+ await (0, import_storage.uploadBytes)(fileRef, data, { customMetadata: metadata });
1147
+ return true;
1148
+ }
1149
+ /** List all blob names directly under `prefix` in the raw bucket. */
1150
+ async listRaw(prefix) {
1151
+ const listRef = (0, import_storage.ref)(this.options.storageRaw, prefix);
1152
+ const result = await (0, import_storage.listAll)(listRef);
1153
+ return result.items.map((item) => item.name);
1154
+ }
1155
+ };
1156
+
1157
+ // libs/js/databases/src/lib/doc/firestore.ts
1158
+ var import_firestore = require("firebase/firestore");
1159
+ var FirestoreDocStore = class {
1160
+ _db;
1161
+ constructor(app) {
1162
+ this._db = (0, import_firestore.getFirestore)(app);
1163
+ }
1164
+ subscribeToDoc(col, id, callback) {
1165
+ const ref6 = (0, import_firestore.doc)(this._db, col, id);
1166
+ return (0, import_firestore.onSnapshot)(ref6, (snapshot) => {
1167
+ callback(snapshot.exists() ? snapshot.data() : null);
1168
+ });
1169
+ }
1170
+ subscribeToCollection(col, filters, callback, options) {
1171
+ const constraints = this._buildConstraints(filters, options);
1172
+ const q = (0, import_firestore.query)((0, import_firestore.collection)(this._db, col), ...constraints);
1173
+ return (0, import_firestore.onSnapshot)(q, (snapshot) => {
1174
+ callback(
1175
+ snapshot.docs.map((d) => ({ id: d.id, data: d.data() }))
1176
+ );
1177
+ });
1178
+ }
1179
+ async getDoc(col, id) {
1180
+ const ref6 = (0, import_firestore.doc)(this._db, col, id);
1181
+ const snapshot = await (0, import_firestore.getDoc)(ref6);
1182
+ return snapshot.exists() ? snapshot.data() : null;
1183
+ }
1184
+ async queryDocs(col, filters, options) {
1185
+ const constraints = this._buildConstraints(filters, options);
1186
+ const q = (0, import_firestore.query)((0, import_firestore.collection)(this._db, col), ...constraints);
1187
+ const snapshot = await (0, import_firestore.getDocs)(q);
1188
+ return snapshot.docs.map((d) => ({ id: d.id, data: d.data() }));
1189
+ }
1190
+ async setDoc(col, id, data) {
1191
+ const ref6 = (0, import_firestore.doc)(this._db, col, id);
1192
+ await (0, import_firestore.setDoc)(ref6, data);
1193
+ }
1194
+ async updateDoc(col, id, data) {
1195
+ const ref6 = (0, import_firestore.doc)(this._db, col, id);
1196
+ await (0, import_firestore.updateDoc)(ref6, data);
1197
+ }
1198
+ async addDoc(col, data) {
1199
+ const ref6 = await (0, import_firestore.addDoc)((0, import_firestore.collection)(this._db, col), data);
1200
+ return ref6.id;
1201
+ }
1202
+ async deleteDoc(col, id) {
1203
+ const ref6 = (0, import_firestore.doc)(this._db, col, id);
1204
+ await (0, import_firestore.deleteDoc)(ref6);
1205
+ }
1206
+ _buildConstraints(filters, options) {
1207
+ const constraints = filters.map(
1208
+ (f) => (0, import_firestore.where)(f.field, f.op, f.value)
1209
+ );
1210
+ const extras = [];
1211
+ if (options?.orderBy) {
1212
+ extras.push((0, import_firestore.orderBy)(options.orderBy.field, options.orderBy.direction));
1213
+ }
1214
+ if (options?.limit) {
1215
+ extras.push((0, import_firestore.limit)(options.limit));
1216
+ }
1217
+ return [...constraints, ...extras];
1218
+ }
1219
+ };
1220
+
1221
+ // libs/js/firebase/src/lib/firebase.ts
1222
+ var CueFirebase = class _CueFirebase {
1223
+ static _instance;
1224
+ _muted = true;
1225
+ _emulator = false;
1226
+ constructor(config, auth, muted = false) {
1227
+ this._muted = muted;
1228
+ this._init(config, auth);
1229
+ }
1230
+ static getInstance(config, auth, muted = false) {
1231
+ if (!_CueFirebase._instance) {
1232
+ if (config === void 0)
1233
+ throw new Error("Config needed for instantiation!");
1234
+ if (!muted)
1235
+ console.info("Creating new CueFirebase instance");
1236
+ _CueFirebase._instance = new _CueFirebase(config, auth, muted);
1237
+ }
1238
+ return _CueFirebase._instance;
1239
+ }
1240
+ _functionAcceptTerms;
1241
+ _functionEmitMessage;
1242
+ _functionGetUserInfo;
1243
+ _functionChangeUserRoleOnProject;
1244
+ _functionInviteUserToProject;
1245
+ _functionRemoveUserFromProject;
1246
+ _storageProcessed;
1247
+ _storageRaw;
1248
+ _storageLogs;
1249
+ _storageChatSessions;
1250
+ _storagePublic;
1251
+ _storagePersistence;
1252
+ _collectionChatSessions;
1253
+ _collectionOrganizations;
1254
+ _collectionProjects;
1255
+ _collectionRDFWriting;
1256
+ _collectionAPIKeys;
1257
+ _collectionUsers;
1258
+ _collectionUserTermsAcceptance;
1259
+ _collectionTiers;
1260
+ _auth;
1261
+ _app;
1262
+ _docStore;
1263
+ _blobStore;
1264
+ get functionAcceptTerms() {
1265
+ return this._functionAcceptTerms;
1266
+ }
1267
+ get functionEmitMessage() {
1268
+ return this._functionEmitMessage;
1269
+ }
1270
+ get functionGetUserInfo() {
1271
+ return this._functionGetUserInfo;
1272
+ }
1273
+ get functionInviteUserToProject() {
1274
+ return this._functionInviteUserToProject;
1275
+ }
1276
+ get functionChangeUserRoleOnProject() {
297
1277
  return this._functionChangeUserRoleOnProject;
298
1278
  }
299
1279
  get functionRemoveUserFromProject() {
@@ -347,6 +1327,14 @@ var CueFirebase = class _CueFirebase {
347
1327
  get auth() {
348
1328
  return this._auth;
349
1329
  }
1330
+ /** Provider-agnostic document store backed by Firestore. */
1331
+ get docStore() {
1332
+ return this._docStore;
1333
+ }
1334
+ /** Provider-agnostic blob store backed by Firebase Storage. */
1335
+ get blobStore() {
1336
+ return this._blobStore;
1337
+ }
350
1338
  get emulatorMode() {
351
1339
  return this._emulator;
352
1340
  }
@@ -383,31 +1371,40 @@ var CueFirebase = class _CueFirebase {
383
1371
  functions,
384
1372
  FUNCTION_REMOVE_USER_FROM_PROJECT_PATH
385
1373
  );
386
- this._storageProcessed = (0, import_storage.getStorage)(app, BUCKET_PROCESSED);
387
- this._storageRaw = (0, import_storage.getStorage)(app, BUCKET_RAW);
388
- this._storageChatSessions = (0, import_storage.getStorage)(app, BUCKET_CHAT_SESSIONS);
389
- this._storageLogs = (0, import_storage.getStorage)(app, BUCKET_LOGS);
390
- this._storagePublic = (0, import_storage.getStorage)(app, BUCKET_PUBLIC);
391
- this._storagePersistence = (0, import_storage.getStorage)(app, BUCKET_PERSISTENCE);
392
- this._collectionChatSessions = (0, import_firestore.collection)(
393
- (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),
394
1382
  COLLECTION_CHAT_SESSIONS
395
1383
  );
396
- this._collectionOrganizations = (0, import_firestore.collection)(
397
- (0, import_firestore.getFirestore)(app),
1384
+ this._collectionOrganizations = (0, import_firestore2.collection)(
1385
+ (0, import_firestore2.getFirestore)(app),
398
1386
  COLLECTION_ORGANIZATIONS
399
1387
  );
400
- this._collectionProjects = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_PROJECTS);
401
- this._collectionRDFWriting = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_RDF_WRITING);
402
- this._collectionAPIKeys = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_API_KEYS);
403
- this._collectionUsers = (0, import_firestore.collection)((0, import_firestore.getFirestore)(app), COLLECTION_USERS);
404
- this._collectionUserTermsAcceptance = (0, import_firestore.collection)(
405
- (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),
406
1394
  COLLECTION_USER_TERMS_ACCEPT
407
1395
  );
408
- 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);
409
1397
  this._auth = auth === void 0 ? (0, import_auth.getAuth)(app) : auth;
410
1398
  this._app = app;
1399
+ this._docStore = new FirestoreDocStore(app);
1400
+ this._blobStore = new CueBlobStorage({
1401
+ storageRaw: this._storageRaw,
1402
+ storageProcessed: this._storageProcessed,
1403
+ storageLogs: this._storageLogs,
1404
+ storageChatSessions: this._storageChatSessions,
1405
+ storagePublic: this._storagePublic,
1406
+ storagePersistence: this._storagePersistence
1407
+ });
411
1408
  this._emulator = config.useEmulator || false;
412
1409
  if (config.useEmulator) {
413
1410
  this.attachEmulators();
@@ -452,14 +1449,14 @@ var CueFirebase = class _CueFirebase {
452
1449
  }
453
1450
  const functions = (0, import_functions.getFunctions)(this._app, GCP_REGION);
454
1451
  (0, import_auth.connectAuthEmulator)(this._auth, authEmulatorUrl);
455
- (0, import_firestore.connectFirestoreEmulator)((0, import_firestore.getFirestore)(this._app), firestoreHost, firestorePort);
1452
+ (0, import_firestore2.connectFirestoreEmulator)((0, import_firestore2.getFirestore)(this._app), firestoreHost, firestorePort);
456
1453
  (0, import_functions.connectFunctionsEmulator)(functions, "localhost", 5001);
457
- (0, import_storage.connectStorageEmulator)(this._storageProcessed, storageHost, storagePort);
458
- (0, import_storage.connectStorageEmulator)(this._storageRaw, storageHost, storagePort);
459
- (0, import_storage.connectStorageEmulator)(this._storageChatSessions, storageHost, storagePort);
460
- (0, import_storage.connectStorageEmulator)(this._storageLogs, storageHost, storagePort);
461
- (0, import_storage.connectStorageEmulator)(this._storagePublic, storageHost, storagePort);
462
- (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);
463
1460
  if (!this._muted)
464
1461
  console.info("Firebase emulators attached");
465
1462
  }
@@ -469,7 +1466,7 @@ var CueFirebase = class _CueFirebase {
469
1466
  var import_uuid = require("uuid");
470
1467
 
471
1468
  // libs/js/sync-tools/src/lib/list-remote-files.ts
472
- var import_storage2 = require("firebase/storage");
1469
+ var import_storage3 = require("firebase/storage");
473
1470
 
474
1471
  // libs/js/prefixes/src/lib/qaecy-prefixes.ts
475
1472
  var qaecyPrefixes = {
@@ -3730,11 +4727,11 @@ async function listRemoteFiles(spaceId, providerId, queryHandler2, verbose = fal
3730
4727
  const storage = firebase.storageRaw;
3731
4728
  if (!storage)
3732
4729
  throw new Error("Firebase storage is not initialized");
3733
- const listRef = (0, import_storage2.ref)(storage, spaceId);
4730
+ const listRef = (0, import_storage3.ref)(storage, spaceId);
3734
4731
  if (verbose)
3735
4732
  console.info(`Listing files in raw space: ${spaceId}`);
3736
4733
  const [blobFiles, locationDataMap] = await Promise.all([
3737
- (0, import_storage2.listAll)(listRef),
4734
+ (0, import_storage3.listAll)(listRef),
3738
4735
  getGraphFiles(providerId, queryHandler2)
3739
4736
  ]);
3740
4737
  const files = [];
@@ -3783,134 +4780,13 @@ async function getGraphFiles(providerId, queryHandler2) {
3783
4780
  }
3784
4781
 
3785
4782
  // libs/js/sync-tools/src/lib/list-local-files.ts
3786
- var import_path2 = require("path");
3787
-
3788
- // libs/js/id-builders/src/lib/id-builders.ts
3789
- var import_uuid2 = require("uuid");
3790
- var replacements = {
3791
- "\xE4": "ae",
3792
- "a\u0308": "ae",
3793
- "\xC4": "AE",
3794
- "\xF6": "oe",
3795
- "\xD6": "OE",
3796
- "\xFC": "ue",
3797
- "u\u0308": "ue",
3798
- "\xDC": "UE",
3799
- "U\u0308": "UE",
3800
- "\xDF": "ss",
3801
- "\xE6": "ae",
3802
- "\xC6": "AE",
3803
- "\xF8": "oe",
3804
- "\xD8": "OE",
3805
- "\xE5": "aa",
3806
- "\xC5": "AA",
3807
- "\xE1": "a",
3808
- "\xC1": "A",
3809
- "\xF0": "d",
3810
- "\xD0": "D",
3811
- "\xE9": "e",
3812
- "\xC9": "E",
3813
- "\xED": "i",
3814
- "\xCD": "I",
3815
- "\xF3": "o",
3816
- "\xD3": "O",
3817
- "\xFA": "u",
3818
- "\xDA": "U",
3819
- "\xFD": "y",
3820
- "\xDD": "Y",
3821
- "\xFE": "th",
3822
- "\xDE": "Th"
3823
- };
3824
- function contextBasedGuid(contextString, verbose = false) {
3825
- const namespace = "daca0510-72b5-48ba-9091-b918ca18136b";
3826
- contextString = replaceSpecialChars(contextString, verbose);
3827
- return (0, import_uuid2.v5)(contextString, namespace);
3828
- }
3829
- function replaceSpecialChars(str, verbose = false) {
3830
- let result = str;
3831
- for (const char in replacements) {
3832
- result = result.replace(new RegExp(char, "g"), replacements[char]);
3833
- }
3834
- if (verbose && result !== str)
3835
- console.info(`${str} -> ${result}`);
3836
- return result;
3837
- }
3838
- function generateFileUUID(filepath, providerId = "") {
3839
- return contextBasedGuid(`${providerId}${filepath}`);
3840
- }
3841
-
3842
- // libs/js/id-builders/src/lib/md5-builder.ts
3843
- var import_spark_md5 = __toESM(require("spark-md5"));
3844
- async function fromReadStream(readStream) {
3845
- return new Promise((resolve2, reject) => {
3846
- const spark = new import_spark_md5.default.ArrayBuffer();
3847
- readStream.on("data", (chunk) => {
3848
- spark.append(chunk);
3849
- });
3850
- readStream.on("end", () => {
3851
- resolve2(spark.end());
3852
- });
3853
- readStream.on("error", (err) => reject(err));
3854
- });
3855
- }
3856
-
3857
- // libs/js/sync-tools/src/lib/helpers/md5-builder-node.ts
3858
- var import_fs = require("fs");
3859
- async function md5FromWorker(filePaths, hashWorkerPath, verbose = false, logIntervalPct = 1) {
3860
- const { WorkerPool: WorkerPool2 } = await Promise.resolve().then(() => (init_worker_pool(), worker_pool_exports));
3861
- const pool = new WorkerPool2(hashWorkerPath);
3862
- const hashes = filePaths.map((filePath) => pool.hashFile(filePath));
3863
- if (verbose) {
3864
- let completed = 0;
3865
- let lastPct = 0;
3866
- hashes.forEach(
3867
- (promise) => promise.then(() => {
3868
- completed++;
3869
- const pct = Math.floor(completed / filePaths.length * 100);
3870
- if (pct - lastPct >= logIntervalPct) {
3871
- lastPct = pct;
3872
- console.info(`MD5 progress: ${completed}/${filePaths.length} (${pct}%)`);
3873
- }
3874
- })
3875
- );
3876
- }
3877
- const ret = await Promise.all(hashes);
3878
- await pool.close();
3879
- return ret;
4783
+ init_src();
4784
+ async function _path() {
4785
+ return import("path");
3880
4786
  }
3881
- async function md5NoWorker(filePaths, verbose) {
3882
- if (verbose)
3883
- console.info(`Calculating MD5 hashes for ${filePaths.length} files...`);
3884
- const concurrency = 50;
3885
- const hashes = [];
3886
- for (let i = 0; i < filePaths.length; i += concurrency) {
3887
- const chunk = filePaths.slice(i, i + concurrency);
3888
- const chunkHashes = await Promise.all(
3889
- chunk.map((f) => {
3890
- const stream = (0, import_fs.createReadStream)(f);
3891
- return fromReadStream(stream);
3892
- })
3893
- );
3894
- hashes.push(...chunkHashes);
3895
- if (verbose) {
3896
- const pct = Math.min(
3897
- 100,
3898
- Math.round((i + chunk.length) / filePaths.length * 100)
3899
- );
3900
- console.info(
3901
- `MD5 progress: ${pct}% (${i + chunk.length}/${filePaths.length})`
3902
- );
3903
- }
3904
- }
3905
- if (verbose)
3906
- console.info(`Calculated MD5 hashes for ${filePaths.length} files.`);
3907
- return hashes;
4787
+ async function _fs() {
4788
+ return import("fs/promises");
3908
4789
  }
3909
-
3910
- // libs/js/sync-tools/src/lib/list-local-files.ts
3911
- var import_promises = require("fs/promises");
3912
- var import_promises2 = require("fs/promises");
3913
- var import_jszip = __toESM(require("jszip"));
3914
4790
  var IGNORED = {
3915
4791
  dirs: ["node_modules", ".git", ".hg", ".svn", ".DS_Store"],
3916
4792
  suffix: [".tmp", ".part", ".crdownload"]
@@ -3944,10 +4820,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
3944
4820
  console.warn(
3945
4821
  `More than 10,000 files found in ${dir}. This may take a while...`
3946
4822
  );
3947
- const hashes = hashWorkerPath ? await md5FromWorker(fullPaths, hashWorkerPath, verbose, logIntervalPct) : await md5NoWorker(fullPaths, verbose);
3948
- 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)));
3949
4828
  return fullPaths.map((f, i) => {
3950
- const relativePath = (0, import_path2.relative)(dir, f);
4829
+ const relativePath = relative(dir, f);
3951
4830
  const md5 = hashes[i];
3952
4831
  const contentUUID = contextBasedGuid(md5);
3953
4832
  const locationUUID = generateFileUUID(relativePath, providerId);
@@ -3957,11 +4836,13 @@ async function listLocalFiles(dir, providerId = "", verbose = false, logInterval
3957
4836
  }
3958
4837
  async function filesInLocalDirRecursive(dir, filterFunc = () => true, excludeDirs = true) {
3959
4838
  const results = [];
4839
+ const { readdir } = await _fs();
4840
+ const { join: join4 } = await _path();
3960
4841
  async function traverseDir(currentDir) {
3961
4842
  try {
3962
- const entries = await (0, import_promises.readdir)(currentDir, { withFileTypes: true });
4843
+ const entries = await readdir(currentDir, { withFileTypes: true });
3963
4844
  for (const entry of entries) {
3964
- const fullPath = (0, import_path2.join)(currentDir, entry.name);
4845
+ const fullPath = join4(currentDir, entry.name);
3965
4846
  if (entry.isFile()) {
3966
4847
  if (filterFunc(entry)) {
3967
4848
  results.push(fullPath);
@@ -4003,23 +4884,26 @@ async function unzipFile(zipPath, recursive = true, options) {
4003
4884
  if (currentDepth > maxRecursionDepth) {
4004
4885
  throw new Error(`Zip extraction aborted: exceeded max recursion depth (${maxRecursionDepth})`);
4005
4886
  }
4887
+ const { mkdir: mkdir2, readFile, writeFile: writeFile2 } = await _fs();
4888
+ const { join: join4 } = await _path();
4889
+ const { default: JSZip } = await import("jszip");
4006
4890
  const targetDir = zipPath.replace(/\.zip$/i, "") + UNZIPPED_SUFFIX;
4007
- await (0, import_promises2.mkdir)(targetDir, { recursive: true });
4008
- const data = await (0, import_promises.readFile)(zipPath);
4009
- 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);
4010
4894
  const extractPromises = [];
4011
4895
  for (const [relativePath, file] of Object.entries(zip.files)) {
4012
4896
  if (!file.dir) {
4013
4897
  const pathParts = relativePath.split("/");
4014
- const fileTargetDir = (0, import_path2.join)(targetDir, ...pathParts.slice(0, -1));
4015
- 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]);
4016
4900
  await ensureDir(fileTargetDir);
4017
4901
  const promise = file.async("nodebuffer").then(async (content) => {
4018
4902
  totalUncompressedSize.value += content.length;
4019
4903
  if (totalUncompressedSize.value > maxUncompressedSize) {
4020
4904
  throw new Error(`Zip extraction aborted: exceeded max uncompressed size (${maxUncompressedSize} bytes)`);
4021
4905
  }
4022
- await (0, import_promises.writeFile)(targetPath, content);
4906
+ await writeFile2(targetPath, content);
4023
4907
  if (recursive && targetPath.toLowerCase().endsWith(".zip")) {
4024
4908
  await unzipFile(targetPath, true, {
4025
4909
  maxUncompressedSize,
@@ -4035,18 +4919,20 @@ async function unzipFile(zipPath, recursive = true, options) {
4035
4919
  await Promise.all(extractPromises);
4036
4920
  }
4037
4921
  async function ensureDir(path) {
4038
- await (0, import_promises2.mkdir)(path, { recursive: true });
4922
+ const { mkdir: mkdir2 } = await _fs();
4923
+ await mkdir2(path, { recursive: true });
4039
4924
  }
4040
4925
  async function deleteUnzipped(dir) {
4926
+ const { rm } = await _fs();
4041
4927
  const paths = await filesInLocalDirRecursive(dir, (entry) => entry.name.toLowerCase().endsWith(UNZIPPED_SUFFIX), false);
4042
4928
  for (const p of paths) {
4043
- await (0, import_promises.rm)(p, { recursive: true, force: true });
4929
+ await rm(p, { recursive: true, force: true });
4044
4930
  }
4045
4931
  }
4046
4932
 
4047
4933
  // apps/desktop/cue-cli/src/helpers/query-handler.ts
4048
4934
  var import_auth2 = require("firebase/auth");
4049
- async function queryHandler(query3, spaceId, useEmulator) {
4935
+ async function queryHandler(query4, spaceId, useEmulator) {
4050
4936
  const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
4051
4937
  const token = await (0, import_auth2.getAuth)().currentUser?.getIdToken();
4052
4938
  const res = await fetch(endpoint, {
@@ -4057,7 +4943,7 @@ async function queryHandler(query3, spaceId, useEmulator) {
4057
4943
  "Content-Type": "application/sparql-query",
4058
4944
  Accept: "application/sparql-results+json"
4059
4945
  },
4060
- body: query3
4946
+ body: query4
4061
4947
  });
4062
4948
  if (!res.ok) {
4063
4949
  console.error(
@@ -4079,13 +4965,138 @@ function fileSizePretty(size) {
4079
4965
 
4080
4966
  // libs/js/cue-sdk/src/lib/cue.ts
4081
4967
  var import_app2 = require("firebase/app");
4968
+ var import_storage5 = require("firebase/storage");
4082
4969
 
4083
4970
  // libs/js/cue-sdk/src/lib/auth.ts
4084
4971
  var import_auth3 = require("firebase/auth");
4972
+
4973
+ // libs/js/cue-sdk/src/lib/signal.ts
4974
+ init_src();
4975
+ var CueSignal = class {
4976
+ _value;
4977
+ _listeners = /* @__PURE__ */ new Set();
4978
+ constructor(initial) {
4979
+ this._value = initial;
4980
+ }
4981
+ get() {
4982
+ return this._value;
4983
+ }
4984
+ set(value) {
4985
+ this._value = value;
4986
+ for (const fn of this._listeners)
4987
+ fn();
4988
+ }
4989
+ subscribe(listener) {
4990
+ this._listeners.add(listener);
4991
+ return () => this._listeners.delete(listener);
4992
+ }
4993
+ /** Returns a read-only view of this signal. */
4994
+ asReadonly() {
4995
+ return this;
4996
+ }
4997
+ };
4998
+ function cueComputed(deps, compute) {
4999
+ const listeners = /* @__PURE__ */ new Set();
5000
+ let cached = compute();
5001
+ let dirty = false;
5002
+ const unsubs = [];
5003
+ const notify = () => {
5004
+ dirty = true;
5005
+ for (const fn of listeners)
5006
+ fn();
5007
+ };
5008
+ for (const dep of deps) {
5009
+ unsubs.push(dep.subscribe(notify));
5010
+ }
5011
+ return {
5012
+ get() {
5013
+ if (dirty) {
5014
+ cached = compute();
5015
+ dirty = false;
5016
+ }
5017
+ return cached;
5018
+ },
5019
+ subscribe(fn) {
5020
+ listeners.add(fn);
5021
+ return () => listeners.delete(fn);
5022
+ },
5023
+ destroy() {
5024
+ for (const unsub of unsubs)
5025
+ unsub();
5026
+ listeners.clear();
5027
+ }
5028
+ };
5029
+ }
5030
+ async function staleWhileRevalidate(query4, fetchFresh, onData, cache) {
5031
+ const cacheKey = contextBasedGuid(query4);
5032
+ let staleId;
5033
+ if (cache) {
5034
+ const stale = await cache.get(cacheKey);
5035
+ if (stale !== void 0) {
5036
+ onData(stale, true);
5037
+ staleId = contextBasedGuid(JSON.stringify(stale));
5038
+ }
5039
+ }
5040
+ const fresh = await fetchFresh();
5041
+ onData(fresh, false);
5042
+ if (cache) {
5043
+ const freshId = contextBasedGuid(JSON.stringify(fresh));
5044
+ if (freshId !== staleId) {
5045
+ cache.set(cacheKey, fresh).catch(
5046
+ (err) => console.error("[staleWhileRevalidate] Cache write failed:", err)
5047
+ );
5048
+ }
5049
+ }
5050
+ return fresh;
5051
+ }
5052
+
5053
+ // libs/js/cue-sdk/src/variables.ts
5054
+ var DEFAULT_SDK_CONFIG = {
5055
+ apiKey: "AIzaSyAiW42QBx9HS4Khu88pCW7MV66IhBAQul0",
5056
+ appId: "1:151132927589:web:d2ffdb377dfadfd23ab88c",
5057
+ measurementId: "G-YT4PK6HGZD"
5058
+ };
5059
+ var FIREBASE_PROJECT_ID = "qaecy-mvp-406413";
5060
+ var FIREBASE_SENDER_ID = "734737865998";
5061
+ var GCP_REGION2 = "europe-west6";
5062
+ var COLLECTION_API_KEYS2 = "apiKeys";
5063
+ var COLLECTION_ORGANIZATIONS2 = "organizations";
5064
+ var COLLECTION_PROJECTS2 = "projects";
5065
+ var BUCKET_CHAT_SESSIONS2 = "spaces_chats_eu_west6";
5066
+ var BUCKET_RAW2 = "spaces_raw_eu_west6";
5067
+ var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
5068
+ var BUCKET_LOGS2 = "spaces_logs_eu_west6";
5069
+ var BUCKET_PUBLIC2 = "cue_public_eu_west6";
5070
+ var BUCKET_PERSISTENCE2 = "db_persistence_eu_west6";
5071
+ var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
5072
+ var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
5073
+ var ENDPOINT_SEARCH = "/assistant/search";
5074
+ var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5075
+ var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5076
+ var ENDPOINT_QLEVER_QUERY = "/qlever-server/qlever/query";
5077
+ var ENDPOINT_QLEVER_UPDATE = "/qlever-server/qlever/update";
5078
+ var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
4085
5079
  var MICROSOFT_PROVIDER_ID = "microsoft.com";
5080
+ var SUPERADMIN_ROLE = "superadmin";
5081
+ var RESOURCE_BASE = "https://cue.qaecy.com/r/";
5082
+
5083
+ // libs/js/cue-sdk/src/lib/auth.ts
4086
5084
  var CueAuth = class {
4087
5085
  _auth;
4088
5086
  _endpoints;
5087
+ _userSignal = new CueSignal(null);
5088
+ _tokenSignal = new CueSignal(null);
5089
+ _isSuperAdminSignal = new CueSignal(false);
5090
+ _userIdsSignal;
5091
+ _stopTokenListener;
5092
+ /** Reactive auth state — emits the signed-in `User`, or `null` when signed out. */
5093
+ user;
5094
+ /** Reactive Firebase ID token — refreshes automatically; `null` when signed out. */
5095
+ token;
5096
+ /** `true` when the current user has the `superadmin` custom claim. */
5097
+ isSuperAdmin;
5098
+ /** All unique UIDs for the current user (Firebase UID + linked provider UIDs). */
5099
+ userIds;
4089
5100
  constructor(app, useEmulator = false, endpoints) {
4090
5101
  this._auth = (0, import_auth3.getAuth)(app);
4091
5102
  this._endpoints = endpoints;
@@ -4094,6 +5105,35 @@ var CueAuth = class {
4094
5105
  disableWarnings: true
4095
5106
  });
4096
5107
  }
5108
+ this.user = this._userSignal.asReadonly();
5109
+ this.token = this._tokenSignal.asReadonly();
5110
+ this.isSuperAdmin = this._isSuperAdminSignal.asReadonly();
5111
+ this._userIdsSignal = cueComputed([this._userSignal], () => {
5112
+ const u = this._userSignal.get();
5113
+ if (!u)
5114
+ return [];
5115
+ const ids = /* @__PURE__ */ new Set([u.uid]);
5116
+ u.providerData.forEach((p) => ids.add(p.uid));
5117
+ return Array.from(ids);
5118
+ });
5119
+ this.userIds = this._userIdsSignal;
5120
+ this._stopTokenListener = (0, import_auth3.onIdTokenChanged)(this._auth, async (user) => {
5121
+ this._userSignal.set(user);
5122
+ if (user) {
5123
+ const token = await user.getIdToken();
5124
+ this._tokenSignal.set(token);
5125
+ const result = await (0, import_auth3.getIdTokenResult)(user);
5126
+ this._isSuperAdminSignal.set(result.claims["role"] === SUPERADMIN_ROLE);
5127
+ } else {
5128
+ this._tokenSignal.set(null);
5129
+ this._isSuperAdminSignal.set(false);
5130
+ }
5131
+ });
5132
+ }
5133
+ /** Stop all internal Firebase listeners. Call when the `Cue` instance is no longer needed. */
5134
+ destroy() {
5135
+ this._stopTokenListener();
5136
+ this._userIdsSignal.destroy?.();
4097
5137
  }
4098
5138
  async signIn(provider, credentials) {
4099
5139
  if (provider === "password") {
@@ -4110,6 +5150,36 @@ var CueAuth = class {
4110
5150
  const result = await (0, import_auth3.signInWithPopup)(this._auth, firebaseProvider);
4111
5151
  return result.user;
4112
5152
  }
5153
+ /**
5154
+ * Initiate a redirect-based sign-in (mobile / iframe contexts where popups
5155
+ * are blocked). Call `checkRedirectResult()` on the next page load to
5156
+ * retrieve the result.
5157
+ */
5158
+ async signInWithRedirect(provider) {
5159
+ const firebaseProvider = provider === "google" ? new import_auth3.GoogleAuthProvider() : new import_auth3.OAuthProvider(MICROSOFT_PROVIDER_ID);
5160
+ await (0, import_auth3.signInWithRedirect)(this._auth, firebaseProvider);
5161
+ }
5162
+ /**
5163
+ * Retrieve the result of a redirect sign-in. Returns the signed-in `User`
5164
+ * or `null` if there is no pending redirect result.
5165
+ * Call this once on app startup before showing a sign-in UI.
5166
+ */
5167
+ async checkRedirectResult() {
5168
+ const result = await (0, import_auth3.getRedirectResult)(this._auth);
5169
+ return result?.user ?? null;
5170
+ }
5171
+ /**
5172
+ * One-shot async check — returns `true` if the current user has the
5173
+ * `superadmin` custom claim. For reactive use, read `cue.auth.isSuperAdmin`
5174
+ * (the signal) instead.
5175
+ */
5176
+ async checkSuperAdmin() {
5177
+ const user = this._auth.currentUser;
5178
+ if (!user)
5179
+ return false;
5180
+ const tokenResult = await (0, import_auth3.getIdTokenResult)(user);
5181
+ return tokenResult.claims["role"] === SUPERADMIN_ROLE;
5182
+ }
4113
5183
  /** Sign in with a Cue API key */
4114
5184
  async signInWithApiKey(cueApiKey, projectId) {
4115
5185
  const response = await fetch(this._endpoints.tokenUrl, {
@@ -4144,6 +5214,30 @@ var CueAuth = class {
4144
5214
  return null;
4145
5215
  return user.getIdToken(forceRefresh);
4146
5216
  }
5217
+ /**
5218
+ * Executes a fetch with a Bearer token. On a 401 response the token is
5219
+ * force-refreshed and the request is retried once before throwing.
5220
+ */
5221
+ async authenticatedFetch(url, init = {}) {
5222
+ const makeRequest = async (forceRefresh) => {
5223
+ const token = await this.getToken(forceRefresh);
5224
+ if (!token)
5225
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5226
+ const { headers: existingHeaders, ...rest } = init;
5227
+ return fetch(url, {
5228
+ ...rest,
5229
+ headers: {
5230
+ ...existingHeaders,
5231
+ Authorization: `Bearer ${token}`
5232
+ }
5233
+ });
5234
+ };
5235
+ let response = await makeRequest(false);
5236
+ if (response.status === 401) {
5237
+ response = await makeRequest(true);
5238
+ }
5239
+ return response;
5240
+ }
4147
5241
  /** Raw Firebase Auth instance, for advanced use cases */
4148
5242
  get firebaseAuth() {
4149
5243
  return this._auth;
@@ -4151,9 +5245,6 @@ var CueAuth = class {
4151
5245
  };
4152
5246
 
4153
5247
  // libs/js/cue-sdk/src/lib/api.ts
4154
- var ENDPOINT_SEARCH = "/assistant/search";
4155
- var ENDPOINT_SPARQL = "/triplestore/query";
4156
- var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
4157
5248
  var CueApi = class {
4158
5249
  constructor(_auth, _gatewayUrl, projects, sync) {
4159
5250
  this._auth = _auth;
@@ -4161,9 +5252,6 @@ var CueApi = class {
4161
5252
  this.projects = projects;
4162
5253
  this.sync = sync;
4163
5254
  }
4164
- async _authHeaders() {
4165
- return this.getAuthHeaders();
4166
- }
4167
5255
  /**
4168
5256
  * Returns standard authentication headers for the current user.
4169
5257
  * Useful when calling Cue-backed services directly (e.g. the GIS proxy).
@@ -4180,20 +5268,24 @@ var CueApi = class {
4180
5268
  /**
4181
5269
  * Search project documents using natural language.
4182
5270
  * The user must be authenticated before calling this.
4183
- */
4184
- async search(request) {
4185
- const headers = await this._authHeaders();
4186
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SEARCH}`, {
4187
- method: "POST",
4188
- headers,
4189
- body: JSON.stringify({
4190
- term: request.term,
4191
- projectId: request.projectId,
4192
- categories: request.categories ?? []
4193
- })
4194
- });
5271
+ */
5272
+ async search(request) {
5273
+ const response = await this._auth.authenticatedFetch(
5274
+ `${this._gatewayUrl}${ENDPOINT_SEARCH}`,
5275
+ {
5276
+ method: "POST",
5277
+ headers: { "Content-Type": "application/json", "cue-project-id": request.projectId },
5278
+ body: JSON.stringify({
5279
+ term: request.term,
5280
+ projectId: request.projectId,
5281
+ categories: request.categories ?? []
5282
+ })
5283
+ }
5284
+ );
4195
5285
  if (!response.ok) {
4196
- throw new Error(`Search request failed: ${response.status} ${response.statusText}`);
5286
+ throw new Error(
5287
+ `Search request failed: ${response.status} ${response.statusText}`
5288
+ );
4197
5289
  }
4198
5290
  return response.json();
4199
5291
  }
@@ -4201,48 +5293,71 @@ var CueApi = class {
4201
5293
  * Execute a SPARQL query against the project's triplestore.
4202
5294
  * The user must be authenticated before calling this.
4203
5295
  */
4204
- async sparql(query3, projectId) {
4205
- const headers = await this._authHeaders();
4206
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SPARQL}`, {
4207
- method: "POST",
4208
- headers,
4209
- body: JSON.stringify({ query: query3, projectId })
4210
- });
5296
+ async sparql(query4, projectId, graphType) {
5297
+ console.log(graphType);
5298
+ const endpoint = graphType === "qlever" ? ENDPOINT_QLEVER_QUERY : ENDPOINT_FUSEKI_QUERY;
5299
+ console.log(`Executing SPARQL query against ${endpoint} for project ${projectId} with graph type ${graphType ?? "fuseki"}`);
5300
+ const urlencoded = new URLSearchParams();
5301
+ urlencoded.append("query", query4);
5302
+ const response = await this._auth.authenticatedFetch(
5303
+ `${this._gatewayUrl}${endpoint}`,
5304
+ {
5305
+ method: "POST",
5306
+ headers: {
5307
+ "Content-Type": "application/x-www-form-urlencoded",
5308
+ "Accept": "application/sparql-results+json",
5309
+ "x-project-id": projectId,
5310
+ "cue-project-id": projectId
5311
+ },
5312
+ body: urlencoded
5313
+ }
5314
+ );
4211
5315
  if (!response.ok) {
4212
- throw new Error(`SPARQL query failed: ${response.status} ${response.statusText}`);
5316
+ throw new Error(
5317
+ `SPARQL query failed: ${response.status} ${response.statusText}`
5318
+ );
4213
5319
  }
4214
5320
  return response.json();
4215
5321
  }
4216
- async getConsumption(projectId, tier) {
4217
- const headers = await this._authHeaders();
4218
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`, {
4219
- headers: {
4220
- ...headers,
4221
- "x-project-id": projectId,
4222
- ...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
+ }
4223
5331
  }
4224
- });
5332
+ );
4225
5333
  if (!response.ok) {
4226
- 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
+ );
4227
5337
  }
4228
5338
  return response.json();
4229
5339
  }
4230
5340
  };
4231
5341
 
4232
5342
  // libs/js/cue-sdk/src/lib/project.ts
4233
- var import_firestore2 = require("firebase/firestore");
4234
- var COLLECTION_PROJECTS2 = "projects";
5343
+ var import_firestore3 = require("firebase/firestore");
5344
+ var import_functions2 = require("firebase/functions");
4235
5345
  var CueProjects = class {
4236
5346
  constructor(_auth, app, useEmulator = false, endpoints) {
4237
5347
  this._auth = _auth;
4238
- this._db = (0, 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 ?? "";
4239
5351
  if (useEmulator) {
4240
5352
  const host = endpoints?.firestoreEmulatorHost ?? "localhost";
4241
5353
  const port = endpoints?.firestoreEmulatorPort ?? 8080;
4242
- (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);
4243
5356
  }
4244
5357
  }
4245
5358
  _db;
5359
+ _functions;
5360
+ _gatewayUrl;
4246
5361
  _requireUser() {
4247
5362
  const user = this._auth.currentUser;
4248
5363
  if (!user)
@@ -4254,60 +5369,31 @@ var CueProjects = class {
4254
5369
  * Throws if a project with the given ID already exists.
4255
5370
  */
4256
5371
  async createProject(options) {
4257
- const userId = this._requireUser();
4258
- const projectId = options.id ?? crypto.randomUUID();
4259
- const now = (/* @__PURE__ */ new Date()).toISOString();
4260
- const projectSettings = { views: [], chatDisabled: false };
4261
- const ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4262
- const existing = await (0, import_firestore2.getDoc)(ref5);
4263
- if (existing.exists()) {
4264
- throw new Error(`Project with ID "${projectId}" already exists.`);
4265
- }
4266
- const data = {
4267
- id: projectId,
4268
- name: options.name,
4269
- organizationID: options.organizationID,
4270
- created: now,
4271
- lastSync: null,
4272
- isPublic: false,
4273
- members: [userId],
4274
- syncers: [userId],
4275
- admins: [userId],
4276
- alternativeIDs: [projectId],
4277
- projectSettings
4278
- };
4279
- await (0, import_firestore2.setDoc)(ref5, data);
4280
- return data;
5372
+ const response = await this._auth.authenticatedFetch(`${this._gatewayUrl}${ENDPOINT_CREATE_PROJECT}`, {
5373
+ method: "POST",
5374
+ headers: { "Content-Type": "application/json" },
5375
+ body: JSON.stringify(options)
5376
+ });
5377
+ if (!response.ok) {
5378
+ const body = await response.text().catch(() => "");
5379
+ throw new Error(`Failed to create project: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`);
5380
+ }
5381
+ return response.json();
4281
5382
  }
4282
5383
  /**
4283
- * List all projects where the authenticated user appears in the members, syncers, or admins array.
4284
- * 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.
4285
5386
  */
4286
5387
  async listProjects() {
4287
5388
  const userId = this._requireUser();
4288
- const col = (0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2);
4289
- const [memberSnap, syncerSnap, adminSnap] = await Promise.all([
4290
- (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("members", "array-contains", userId))),
4291
- (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("syncers", "array-contains", userId))),
4292
- (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("admins", "array-contains", userId)))
4293
- ]);
4294
- const seen = /* @__PURE__ */ new Set();
4295
- const results = [];
4296
- for (const snap of [memberSnap, syncerSnap, adminSnap]) {
4297
- for (const d of snap.docs) {
4298
- const project = d.data();
4299
- if (!seen.has(project.id)) {
4300
- seen.add(project.id);
4301
- results.push(project);
4302
- }
4303
- }
4304
- }
4305
- return results;
5389
+ const col = (0, import_firestore3.collection)(this._db, COLLECTION_PROJECTS2);
5390
+ const snap = await (0, import_firestore3.getDocs)((0, import_firestore3.query)(col, (0, import_firestore3.where)("members", "array-contains", userId), (0, import_firestore3.limit)(100)));
5391
+ return snap.docs.map((d) => d.data());
4306
5392
  }
4307
5393
  /** Fetch a single project by ID. Returns null if not found. */
4308
5394
  async getProject(projectId) {
4309
- const ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4310
- 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);
4311
5397
  if (!snap.exists())
4312
5398
  return null;
4313
5399
  return snap.data();
@@ -4319,28 +5405,49 @@ var CueProjects = class {
4319
5405
  async incrementUnitsConsumed(projectId, units, userId) {
4320
5406
  if (units <= 0)
4321
5407
  return;
4322
- const ref5 = (0, import_firestore2.doc)(this._db, "clientSync", projectId);
4323
- await (0, import_firestore2.setDoc)(ref5, {
4324
- unitsConsumed: (0, import_firestore2.increment)(units),
4325
- 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)(),
4326
5412
  lastUserId: userId
4327
5413
  }, { merge: true });
4328
5414
  }
5415
+ /**
5416
+ * Invite a user to a project by email. Returns the invited user's uid and display name.
5417
+ */
5418
+ async inviteUserToProject(email, projectId, role) {
5419
+ const fn = (0, import_functions2.httpsCallable)(this._functions, "inviteUserToProject");
5420
+ const res = await fn({ email, spaceId: projectId, role });
5421
+ return res.data;
5422
+ }
5423
+ /** Change an existing member's role on a project. */
5424
+ async changeUserRoleOnProject(uid, projectId, role) {
5425
+ const fn = (0, import_functions2.httpsCallable)(this._functions, "changeUserRoleOnProject");
5426
+ await fn({ uid, spaceId: projectId, role });
5427
+ }
5428
+ /** Remove a member from a project. */
5429
+ async removeUserFromProject(uid, projectId) {
5430
+ const fn = (0, import_functions2.httpsCallable)(this._functions, "removeUserFromProject");
5431
+ await fn({ uid, spaceId: projectId });
5432
+ }
4329
5433
  };
4330
5434
 
4331
5435
  // libs/js/cue-sdk/src/lib/profile.ts
4332
5436
  var import_auth4 = require("firebase/auth");
4333
- var import_firestore3 = require("firebase/firestore");
4334
- var COLLECTION_API_KEYS2 = "apiKeys";
5437
+ var import_firestore4 = require("firebase/firestore");
5438
+ var import_functions3 = require("firebase/functions");
4335
5439
  var CueProfile = class {
4336
5440
  constructor(_auth, app, useEmulator, emulatorHost, emulatorPort) {
4337
5441
  this._auth = _auth;
4338
- this._db = (0, import_firestore3.getFirestore)(app);
5442
+ this._db = (0, import_firestore4.getFirestore)(app);
5443
+ this._functions = (0, import_functions3.getFunctions)(app, GCP_REGION2);
4339
5444
  if (useEmulator) {
4340
- (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);
4341
5447
  }
4342
5448
  }
4343
5449
  _db;
5450
+ _functions;
4344
5451
  _apiKeyDocRef;
4345
5452
  /** Whether the current user has an active API key. */
4346
5453
  async hasAPIKey() {
@@ -4421,512 +5528,1197 @@ var CueProfile = class {
4421
5528
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4422
5529
  const randomKey = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(150)), (b) => alphabet[b & 63]).join("");
4423
5530
  const data = { key: `cue-${randomKey}`, uid, expiration };
4424
- const col = (0, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4425
- 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);
4426
5533
  return data;
4427
5534
  }
4428
5535
  /** Revokes the current user's API key. */
4429
5536
  async revokeAPIKey() {
4430
5537
  if (!this._apiKeyDocRef)
4431
5538
  throw new Error("No API key document reference");
4432
- await (0, import_firestore3.deleteDoc)(this._apiKeyDocRef);
5539
+ await (0, import_firestore4.deleteDoc)(this._apiKeyDocRef);
4433
5540
  this._apiKeyDocRef = void 0;
4434
5541
  }
4435
5542
  /** Fetches the current user's existing API key. */
4436
5543
  async requestAPIKey() {
4437
5544
  if (!this._apiKeyDocRef)
4438
5545
  throw new Error("No API key document reference");
4439
- const doc2 = (await (0, import_firestore3.getDoc)(this._apiKeyDocRef)).data();
4440
- 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 };
4441
5548
  }
4442
5549
  async _checkIfUserHasAPIKey(uid) {
4443
- const col = (0, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4444
- const q = (0, import_firestore3.query)(col, (0, import_firestore3.where)("uid", "==", uid), (0, import_firestore3.limit)(1));
4445
- 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);
4446
5553
  if (!snapshot.empty)
4447
5554
  this._apiKeyDocRef = snapshot.docs[0].ref;
4448
5555
  return !snapshot.empty;
4449
5556
  }
5557
+ /** Returns all organizations the current user is a member of. */
5558
+ async listOrganizations() {
5559
+ const uid = this._auth.currentUser?.uid;
5560
+ if (!uid)
5561
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5562
+ const col = (0, import_firestore4.collection)(this._db, COLLECTION_ORGANIZATIONS2);
5563
+ const q = (0, import_firestore4.query)(col, (0, import_firestore4.where)("members", "array-contains", uid));
5564
+ const snap = await (0, import_firestore4.getDocs)(q);
5565
+ return snap.docs.map((d) => d.data());
5566
+ }
4450
5567
  _requireUser() {
4451
5568
  const user = this._auth.currentUser;
4452
5569
  if (!user)
4453
5570
  throw new Error("Not authenticated");
4454
5571
  return user;
4455
5572
  }
5573
+ /**
5574
+ * Fetch display name and email for a list of user UIDs.
5575
+ * Uses the `getUserInfo` Firebase callable function.
5576
+ */
5577
+ async getUserInfo(uids) {
5578
+ const fn = (0, import_functions3.httpsCallable)(this._functions, "getUserInfo");
5579
+ const res = await fn({ uids });
5580
+ return res.data;
5581
+ }
5582
+ /** Record that the current user has accepted the terms of service. */
5583
+ async acceptTerms() {
5584
+ const fn = (0, import_functions3.httpsCallable)(this._functions, "acceptTerms");
5585
+ await fn({});
5586
+ }
4456
5587
  };
4457
5588
 
4458
- // libs/js/cue-sdk/src/lib/cue.ts
4459
- var FIREBASE_PROJECT_ID = "qaecy-mvp-406413";
4460
- var FIREBASE_SENDER_ID = "734737865998";
4461
- var ENDPOINTS = {
4462
- production: {
4463
- gatewayUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app",
4464
- tokenUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token",
4465
- authEmulatorUrl: "http://localhost:9099",
4466
- storageEmulatorHost: "localhost",
4467
- storageEmulatorPort: 9199,
4468
- firestoreEmulatorHost: "localhost",
4469
- firestoreEmulatorPort: 8080
4470
- },
4471
- emulator: {
4472
- gatewayUrl: "http://localhost:8093",
4473
- tokenUrl: "http://localhost:8093/token",
4474
- authEmulatorUrl: "http://localhost:9099",
4475
- storageEmulatorHost: "localhost",
4476
- storageEmulatorPort: 9199,
4477
- firestoreEmulatorHost: "localhost",
4478
- firestoreEmulatorPort: 8080
4479
- }
5589
+ // libs/js/cue-sdk/src/lib/privileges.ts
5590
+ var ROLE_HIERARCHY = ["superadmin", "admin", "syncer", "member"];
5591
+ var REQUIRED_ROLES = {
5592
+ createEntities: "admin",
5593
+ createProvider: "superadmin",
5594
+ changeContentCategories: "syncer",
5595
+ deleteDocuments: "superadmin",
5596
+ deleteUserFromProject: "admin",
5597
+ downloadDocuments: "member",
5598
+ editContentCategories: "syncer",
5599
+ editPublicReposAvailableToAgent: "admin",
5600
+ editTier: "admin",
5601
+ inviteUserToProject: "member",
5602
+ renameDocuments: "superadmin",
5603
+ uploadDocuments: "syncer",
5604
+ viewEntities: "member"
4480
5605
  };
4481
- var Cue = class {
4482
- auth;
4483
- api;
4484
- profile;
4485
- _app;
4486
- _endpoints;
4487
- _isEmulator;
4488
- constructor(config) {
4489
- const env = config.environment ?? "production";
4490
- this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
4491
- this._isEmulator = env === "emulator";
4492
- this._app = (0, import_app2.initializeApp)({
4493
- apiKey: config.apiKey,
4494
- appId: config.appId,
4495
- measurementId: config.measurementId,
4496
- authDomain: `${FIREBASE_PROJECT_ID}.firebaseapp.com`,
4497
- projectId: FIREBASE_PROJECT_ID,
4498
- messagingSenderId: FIREBASE_SENDER_ID
4499
- });
4500
- this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
4501
- const projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
4502
- this.api = this._buildApi(projects);
4503
- this.profile = new CueProfile(
4504
- this.auth,
4505
- this._app,
4506
- this._isEmulator,
4507
- this._endpoints.firestoreEmulatorHost,
4508
- this._endpoints.firestoreEmulatorPort
5606
+ function defaultPrivileges() {
5607
+ return {
5608
+ changeContentCategories: false,
5609
+ createEntities: false,
5610
+ createProvider: false,
5611
+ deleteDocuments: false,
5612
+ deleteUserFromProject: false,
5613
+ downloadDocuments: false,
5614
+ editContentCategories: false,
5615
+ editPublicReposAvailableToAgent: false,
5616
+ editTier: false,
5617
+ inviteUserToProject: false,
5618
+ renameDocuments: false,
5619
+ uploadDocuments: false,
5620
+ viewEntities: false
5621
+ };
5622
+ }
5623
+ var CuePrivileges = class {
5624
+ constructor(_isSuperAdmin) {
5625
+ this._isSuperAdmin = _isSuperAdmin;
5626
+ this._projectRoles = new CueSignal([]);
5627
+ this.privileges = cueComputed(
5628
+ [this._projectRoles, _isSuperAdmin],
5629
+ () => this._compute()
4509
5630
  );
4510
5631
  }
4511
- /** Override in subclasses to provide a custom CueApi (e.g. with sync). */
4512
- _buildApi(projects) {
4513
- 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
+ }
4514
5653
  }
4515
- /** Convenience: get the current user's Firebase ID token */
4516
- getToken(forceRefresh = false) {
4517
- 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;
4518
5670
  }
4519
5671
  };
4520
5672
 
4521
- // libs/js/cue-sdk/src/lib/cue-node.ts
5673
+ // libs/js/cue-sdk/src/lib/cache.ts
4522
5674
  var import_storage4 = require("firebase/storage");
4523
-
4524
- // libs/js/databases/src/lib/graph/fuseki.ts
4525
- var Fuseki = class _Fuseki {
4526
- queryEndpoint;
4527
- updateEndpoint;
4528
- baseHeaders;
4529
- static RELEVANT_HEADER_KEYS = [
4530
- "authorization",
4531
- "Authorization",
4532
- "x-project-id"
4533
- ];
4534
- constructor(graphOptions) {
4535
- this.queryEndpoint = graphOptions.queryEndpoint;
4536
- this.updateEndpoint = graphOptions.updateEndpoint;
4537
- this.baseHeaders = Object.fromEntries(
4538
- Object.entries(graphOptions.originalHeaders || {}).filter(
4539
- ([key]) => _Fuseki.RELEVANT_HEADER_KEYS.includes(key)
4540
- )
4541
- );
4542
- if (graphOptions.authHeader !== void 0) {
4543
- this.baseHeaders["Authorization"] = graphOptions.authHeader;
4544
- }
5675
+ var CueCache = class {
5676
+ constructor(_storage) {
5677
+ this._storage = _storage;
4545
5678
  }
4546
- async ping() {
4547
- const query3 = "ASK { }";
4548
- const res = await this.query(query3);
4549
- return res.boolean;
5679
+ // ── Query cache ────────────────────────────────────────────────────────────
5680
+ async getQueryCache(projectId, id) {
5681
+ return this._get(`portal/projects/${projectId}/queries`, id);
4550
5682
  }
4551
- async query(query3, accept = "application/sparql-results+json") {
4552
- 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) {
4553
5706
  try {
4554
- res = await fetch(this.queryEndpoint, {
4555
- headers: {
4556
- ...this.baseHeaders,
4557
- "Content-Type": "application/x-www-form-urlencoded",
4558
- Accept: accept
4559
- },
4560
- method: "POST",
4561
- body: new URLSearchParams({ query: query3 })
4562
- });
5707
+ const blob = await (0, import_storage4.getBlob)((0, import_storage4.ref)(this._storage, `${basePath}/${id}.json.gz`));
5708
+ const compressed = await blob.arrayBuffer();
5709
+ const ds = new DecompressionStream("gzip");
5710
+ const writer = ds.writable.getWriter();
5711
+ const reader = ds.readable.getReader();
5712
+ writer.write(new Uint8Array(compressed));
5713
+ writer.close();
5714
+ const chunks = [];
5715
+ let result = await reader.read();
5716
+ while (!result.done) {
5717
+ chunks.push(result.value);
5718
+ result = await reader.read();
5719
+ }
5720
+ const totalLength = chunks.reduce((s, c) => s + c.length, 0);
5721
+ const out = new Uint8Array(totalLength);
5722
+ let offset = 0;
5723
+ for (const chunk of chunks) {
5724
+ out.set(chunk, offset);
5725
+ offset += chunk.length;
5726
+ }
5727
+ return JSON.parse(new TextDecoder().decode(out));
4563
5728
  } catch (err) {
4564
- throw new Error(
4565
- `Fuseki is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
4566
- );
5729
+ if (_isNotFound(err))
5730
+ return void 0;
5731
+ throw err;
4567
5732
  }
4568
- if (!res.ok) {
4569
- const body = await res.text();
4570
- 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();
4571
5746
  }
4572
- 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
+ );
4573
5759
  }
4574
- async subset(query3, accept = "text/turtle") {
4575
- const res = await fetch(this.queryEndpoint, {
4576
- headers: {
4577
- ...this.baseHeaders,
4578
- "Content-Type": "application/x-www-form-urlencoded",
4579
- Authorization: this.baseHeaders["Authorization"] || "",
4580
- 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 };
4581
5839
  },
4582
- method: "POST",
4583
- 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
+ };
4584
5878
  });
4585
- if (accept === "application/ld+json") {
4586
- return await res.json();
4587
- }
4588
- return await res.text();
4589
5879
  }
4590
- async update(update) {
4591
- const res = await fetch(this.updateEndpoint, {
4592
- headers: {
4593
- ...this.baseHeaders,
4594
- "Content-Type": "application/x-www-form-urlencoded"
4595
- },
4596
- method: "POST",
4597
- 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
+ };
4598
5906
  });
4599
- if (!res.ok) {
4600
- const body = await res.text();
4601
- throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
4602
- }
4603
- const contentType = res.headers.get("content-type") ?? "";
4604
- if (contentType.includes("application/json")) {
4605
- return await res.json();
4606
- }
4607
- return {
4608
- ok: res.ok,
4609
- status: res.status,
4610
- message: await res.text()
4611
- };
4612
5907
  }
4613
5908
  };
4614
5909
 
4615
- // libs/js/databases/src/lib/graph/qlever.ts
4616
- var import_zlib = require("zlib");
4617
- var import_n3 = require("n3");
4618
- var QLeverLockedError = class extends Error {
4619
- constructor(body) {
4620
- super(`QLever is locked (rebuild in progress): ${body}`);
4621
- 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
+ );
4622
6017
  }
4623
- };
4624
- var QLever = class _QLever {
4625
- queryEndpoint;
4626
- updateEndpoint;
4627
- dataEndpoint;
4628
- baseHeaders;
4629
- static RELEVANT_HEADER_KEYS = [
4630
- "authorization",
4631
- "Authorization",
4632
- "x-project-id",
4633
- "x-user-roles"
4634
- // add more if needed
4635
- ];
4636
- /** Max retries on 423 Locked (rebuild in progress). */
4637
- static LOCKED_MAX_RETRIES = parseInt(process.env["QLEVER_LOCKED_MAX_RETRIES"] ?? "10", 10);
4638
- /** Base delay (ms) for exponential backoff on 423. */
4639
- static LOCKED_BASE_DELAY_MS = parseInt(process.env["QLEVER_LOCKED_BASE_DELAY_MS"] ?? "2000", 10);
4640
6018
  /**
4641
- * Retry an async write operation on 423 Locked with exponential backoff + jitter.
4642
- * 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.
4643
6024
  */
4644
- static async _retryOnLocked(fn) {
4645
- const maxRetries = _QLever.LOCKED_MAX_RETRIES;
4646
- const baseDelayMs = _QLever.LOCKED_BASE_DELAY_MS;
4647
- let lastError;
4648
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
4649
- try {
4650
- return await fn();
4651
- } catch (err) {
4652
- lastError = err;
4653
- if (err instanceof QLeverLockedError && attempt < maxRetries) {
4654
- const jitter = 0.5 + Math.random();
4655
- const delay = baseDelayMs * Math.pow(2, attempt) * jitter;
4656
- await new Promise((resolve2) => setTimeout(resolve2, delay));
4657
- continue;
4658
- }
4659
- throw err;
4660
- }
6025
+ async requestEntityLocations(uuids) {
6026
+ const newUUIDs = uuids.filter((id) => this._entityOSMMap.get()[id] === void 0);
6027
+ if (newUUIDs.length === 0)
6028
+ return;
6029
+ const osmInit = { ...this._entityOSMMap.get() };
6030
+ for (const id of newUUIDs)
6031
+ osmInit[id] = { direct: [], indirect: [] };
6032
+ this._entityOSMMap.set(osmInit);
6033
+ const values = newUUIDs.map((id) => `r:${id}`).join(" ");
6034
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
6035
+ PREFIX r: <${this.baseURL}>
6036
+ SELECT ?id ?osm ?direct ?rels ?entityUUID
6037
+ WHERE {
6038
+ VALUES ?iri { ${values} }
6039
+ {
6040
+ SELECT ?iri ?osm ?direct
6041
+ WHERE {
6042
+ ?iri qcy:similarTo ?osm .
6043
+ FILTER STRSTARTS(STR(?osm), "https://www.openstreetmap.org/")
6044
+ BIND(true AS ?direct)
4661
6045
  }
4662
- throw lastError;
4663
6046
  }
4664
- constructor(graphOptions) {
4665
- this.queryEndpoint = graphOptions.queryEndpoint;
4666
- this.updateEndpoint = graphOptions.updateEndpoint;
4667
- this.dataEndpoint = this.updateEndpoint.replace(/\/update$/, "/data");
4668
- this.baseHeaders = Object.fromEntries(
4669
- Object.entries(graphOptions.originalHeaders || {}).filter(
4670
- ([key]) => _QLever.RELEVANT_HEADER_KEYS.includes(key)
4671
- )
4672
- );
4673
- 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);
4674
6081
  }
4675
- async ping() {
4676
- const query3 = "ASK { }";
4677
- const res = await this.query(query3);
4678
- 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;
4679
6103
  }
4680
- async query(query3, accept = "application/sparql-results+json") {
4681
- let res;
4682
- try {
4683
- res = await fetch(this.queryEndpoint, {
4684
- headers: {
4685
- ...this.baseHeaders,
4686
- "Content-Type": "application/x-www-form-urlencoded",
4687
- Accept: accept
4688
- },
4689
- method: "POST",
4690
- body: new URLSearchParams({ query: query3 })
4691
- });
4692
- } catch (err) {
4693
- throw new Error(
4694
- `QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
4695
- );
6104
+ /**
6105
+ * Fetches UUIDs of documents that reference the given entity IRI.
6106
+ * Also triggers a core-data fetch for the entity itself if not yet loaded.
6107
+ *
6108
+ * Returns document UUIDs. Full document data will be populated by the
6109
+ * document service (future `CueProjectDocuments`).
6110
+ */
6111
+ async fetchEntityDocuments(iri) {
6112
+ const uuid = iri.replace(/^.*\/([^/]*)$/, "$1");
6113
+ this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: [] });
6114
+ if (this._entityDetails.get()[uuid] === void 0) {
6115
+ this.requestEntityData([uuid]);
4696
6116
  }
4697
- if (!res.ok) {
4698
- const body = await res.text();
4699
- 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
+ };
4700
6188
  }
4701
- return await res.json();
6189
+ return result;
4702
6190
  }
4703
- async subset(query3, accept = "text/turtle") {
4704
- const res = await fetch(this.queryEndpoint, {
4705
- headers: {
4706
- ...this.baseHeaders,
4707
- "Content-Type": "application/x-www-form-urlencoded",
4708
- Accept: accept
4709
- },
4710
- method: "POST",
4711
- 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
+ };
4712
6221
  });
4713
- return await res.text();
6222
+ this._entityDetails.set(detailsUpdate);
6223
+ return result;
4714
6224
  }
4715
- async update(update) {
4716
- return _QLever._retryOnLocked(async () => {
4717
- const res = await fetch(this.updateEndpoint, {
4718
- headers: {
4719
- ...this.baseHeaders,
4720
- "Content-Type": "application/x-www-form-urlencoded"
4721
- },
4722
- method: "POST",
4723
- 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
4724
6249
  });
4725
- if (!res.ok) {
4726
- const body = await res.text();
4727
- if (res.status === 423)
4728
- throw new QLeverLockedError(body);
4729
- 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);
4730
6303
  }
4731
- 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);
4732
6336
  });
6337
+ this._osmWKTMap.set(update);
4733
6338
  }
6339
+ };
6340
+
6341
+ // libs/js/cue-sdk/src/lib/documents.ts
6342
+ var CueProjectDocuments = class {
6343
+ constructor(_api, _projectId, language, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
6344
+ this._api = _api;
6345
+ this._projectId = _projectId;
6346
+ this._queryCache = _queryCache;
6347
+ this._graphType = _graphType;
6348
+ this.baseURL = `${rdfBase}${_projectId}/`;
6349
+ this._language = language;
6350
+ this.documentInfoMap = this._documentInfoMap.asReadonly();
6351
+ this.projectDocumentsData = this._projectDocumentsData.asReadonly();
6352
+ }
6353
+ /** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
6354
+ baseURL;
6355
+ _language;
6356
+ _documentInfoMap = new CueSignal({});
6357
+ _projectDocumentsData = new CueSignal({
6358
+ duplicateCount: 0,
6359
+ documentsBySuffix: {},
6360
+ documentsByContentCategory: {}
6361
+ });
6362
+ /** Lazily populated per-document detail map. */
6363
+ documentInfoMap;
6364
+ /** Project-level document overview (grouped counts + sizes). */
6365
+ projectDocumentsData;
6366
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
4734
6367
  /**
4735
- * Insert quads via the /data endpoint, grouped by named graph.
4736
- * This is preferred over SPARQL INSERT DATA for QLever because
4737
- * the /data endpoint correctly registers named graphs in the index.
6368
+ * Resets all document state. Call when the active project changes.
6369
+ * Follow with `fetchOverview()` once the triplestore is ready.
4738
6370
  */
4739
- async insertData(quads) {
4740
- await this._postToDataEndpoint(quads, this.dataEndpoint);
6371
+ reset() {
6372
+ this._documentInfoMap.set({});
6373
+ this._projectDocumentsData.set({
6374
+ duplicateCount: 0,
6375
+ documentsBySuffix: {},
6376
+ documentsByContentCategory: {}
6377
+ });
4741
6378
  }
4742
6379
  /**
4743
- * 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.
4744
6383
  */
4745
- async deleteData(quads) {
4746
- await this._postToDataEndpoint(quads, `${this.dataEndpoint}/delete`);
4747
- }
4748
- async _postToDataEndpoint(quads, baseUrl) {
4749
- const nquads = await this._quadsToNQuads(quads);
4750
- const body = new Uint8Array((0, import_zlib.gzipSync)(Buffer.from(nquads, "utf-8")));
4751
- await _QLever._retryOnLocked(async () => {
4752
- const res = await fetch(baseUrl, {
4753
- method: "POST",
4754
- headers: {
4755
- ...this.baseHeaders,
4756
- "Content-Type": "application/n-quads",
4757
- "Content-Encoding": "gzip"
4758
- },
4759
- body
4760
- });
4761
- if (!res.ok) {
4762
- const text = await res.text();
4763
- if (res.status === 423)
4764
- throw new QLeverLockedError(text);
4765
- throw new Error(`QLever data POST failed (HTTP ${res.status}): ${text}`);
4766
- }
4767
- });
6384
+ setLanguage(lang) {
6385
+ if (this._language === lang)
6386
+ return;
6387
+ this._language = lang;
6388
+ this._documentInfoMap.set({});
4768
6389
  }
4769
- _quadsToNQuads(quads) {
4770
- return new Promise((resolve2, reject) => {
4771
- const writer = new import_n3.Writer({ format: "application/n-quads" });
4772
- writer.addQuads(quads);
4773
- 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: {}
4774
6401
  });
6402
+ const qSuffix = this._buildDocumentsBySuffixQuery();
6403
+ const qCategory = this._buildDocumentsByContentCategoryQuery();
6404
+ const qDuplicates = this._buildDuplicateCountQuery();
6405
+ await staleWhileRevalidate(
6406
+ qSuffix + qCategory + qDuplicates,
6407
+ async () => {
6408
+ const [bySuffix, byCategory, duplicateCount] = await Promise.all([
6409
+ this._runDocumentsBySuffixQuery(qSuffix),
6410
+ this._runDocumentsByContentCategoryQuery(qCategory),
6411
+ this._runDuplicateCountQuery(qDuplicates)
6412
+ ]);
6413
+ return { duplicateCount, documentsBySuffix: bySuffix, documentsByContentCategory: byCategory };
6414
+ },
6415
+ (overview) => this._projectDocumentsData.set(overview),
6416
+ this._queryCache
6417
+ );
4775
6418
  }
4776
- };
6419
+ /**
6420
+ * Lazily batch-fetches core metadata for the given document UUIDs.
6421
+ * Already-cached UUIDs are skipped. Data is merged into `documentInfoMap`
6422
+ * once the SPARQL response arrives.
6423
+ */
6424
+ requestDocumentData(uuids) {
6425
+ const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
6426
+ if (newUUIDs.length === 0)
6427
+ return;
6428
+ const values = newUUIDs.map((id) => `r:${id}`).join(" ");
6429
+ const lang = this._language;
6430
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
6431
+ PREFIX r: <${this.baseURL}>
6432
+ SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
6433
+ (SAMPLE(?fp) AS ?path)
6434
+ (GROUP_CONCAT(DISTINCT ?tag; SEPARATOR=";") AS ?tags)
6435
+ (GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories)
6436
+ WHERE {
6437
+ VALUES ?contentIRI { ${values} }
6438
+ ?contentIRI qcy:sizeBytes ?size ;
6439
+ qcy:hasFileLocation ?loc .
6440
+ ?loc qcy:filePath ?fp ;
6441
+ qcy:suffix ?suffix .
6442
+ OPTIONAL { ?contentIRI qcy:hasContentCategory ?cat }
6443
+ OPTIONAL { ?contentIRI qcy:tag ?tag }
6444
+ OPTIONAL { ?loc qcy:remoteProviderId ?pid }
4777
6445
 
4778
- // libs/js/databases/src/lib/graph/main.ts
4779
- var CueGraphDatabase = class {
4780
- constructor(options) {
4781
- this.options = options;
4782
- switch (this.options.graphType) {
4783
- case "qlever":
4784
- this._db = new QLever(this.options);
4785
- break;
4786
- case "fuseki":
4787
- this._db = new Fuseki(this.options);
4788
- break;
4789
- default:
4790
- 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 }
4791
6497
  }
4792
6498
  }
4793
- _db;
4794
- ping() {
4795
- 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;
4796
6515
  }
4797
- query(queryString, accept) {
4798
- return this._db.query(queryString, accept);
6516
+ async _fetchDocumentsByContentCategory() {
6517
+ return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
4799
6518
  }
4800
- subset(queryString, accept) {
4801
- if (this.options.graphType === "qlever") {
4802
- if (accept && accept !== "text/turtle") {
4803
- return Promise.reject(
4804
- new Error(
4805
- `QLever only supports 'text/turtle' for CONSTRUCT/DESCRIBE queries.`
4806
- )
4807
- );
4808
- }
4809
- return this._db.subset(
4810
- queryString,
4811
- accept
4812
- );
6519
+ _buildDocumentsByContentCategoryQuery() {
6520
+ return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
6521
+ SELECT ?cat (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
6522
+ WHERE {
6523
+ {
6524
+ SELECT DISTINCT ?fc ?cat ?size
6525
+ WHERE {
6526
+ ?fc a qcy:FileContent ;
6527
+ qcy:hasContentCategory ?cat ;
6528
+ qcy:sizeBytes ?size .
6529
+ FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
4813
6530
  }
4814
- return this._db.subset(queryString, accept);
4815
6531
  }
4816
- update(updateString) {
4817
- 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;
4818
6548
  }
4819
- /** Returns true if this backend supports the /data bulk-insert endpoint (QLever only). */
4820
- supportsDataEndpoint() {
4821
- 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);
6626
+ }
6627
+ /**
6628
+ * Lazily fetch OSM location data for the given entity UUIDs.
6629
+ * Already-fetched UUIDs are skipped. Populates `entityInfoMap` geometry fields.
6630
+ */
6631
+ async requestEntityLocations(uuids) {
6632
+ if (this._destroyed)
6633
+ return;
6634
+ return this.entities.requestEntityLocations(uuids);
4822
6635
  }
4823
6636
  /**
4824
- * Insert quads using the backend's preferred bulk-insert mechanism.
4825
- * For QLever: uses the /data endpoint (correctly registers named graphs).
4826
- * For Fuseki: falls back to SPARQL INSERT DATA.
6637
+ * Fetch incoming and outgoing relationships for a single entity IRI.
6638
+ * Result is stored in `entityInfoMap[uuid].relationshipData`.
4827
6639
  */
4828
- insertData(quads) {
4829
- if (this.options.graphType === "qlever") {
4830
- return this._db.insertData(quads);
4831
- }
4832
- return Promise.reject(new Error("insertData not supported for Fuseki \u2014 use update() with SPARQL INSERT DATA"));
6640
+ async fetchEntityRelationships(iri) {
6641
+ if (this._destroyed)
6642
+ throw new Error("CueProjectView is destroyed");
6643
+ return this.entities.fetchEntityRelationships(iri);
4833
6644
  }
4834
6645
  /**
4835
- * Delete quads using the backend's preferred bulk-delete mechanism.
4836
- * For QLever: uses the /data/delete endpoint.
4837
- * For Fuseki: falls back to SPARQL DELETE DATA.
6646
+ * Fetch UUIDs of documents that reference the given entity IRI.
6647
+ * Result is stored in `entityInfoMap[uuid].documentRefs`.
4838
6648
  */
4839
- deleteData(quads) {
4840
- if (this.options.graphType === "qlever") {
4841
- return this._db.deleteData(quads);
4842
- }
4843
- return Promise.reject(new Error("deleteData not supported for Fuseki \u2014 use update() with SPARQL DELETE DATA"));
6649
+ async fetchEntityDocuments(iri) {
6650
+ if (this._destroyed)
6651
+ throw new Error("CueProjectView is destroyed");
6652
+ return this.entities.fetchEntityDocuments(iri);
4844
6653
  }
4845
- };
4846
-
4847
- // libs/js/databases/src/lib/blob/blob.ts
4848
- var import_storage3 = require("firebase/storage");
4849
- var CueBlobStorage = class {
4850
- constructor(options) {
4851
- this.options = options;
6654
+ /** Constructs the full RDF IRI for an entity UUID. */
6655
+ entityIri(uuid) {
6656
+ return this.entities.entityIri(uuid);
4852
6657
  }
6658
+ // ── Document methods ───────────────────────────────────────────────────────
4853
6659
  /**
4854
- * 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`.
4855
6662
  */
4856
- async uploadRaw(blobName, data, metadata, maxRetries = 3) {
4857
- const fileRef = (0, import_storage3.ref)(this.options.storageRaw, blobName);
4858
- let attempt = 0;
4859
- let lastError;
4860
- while (attempt < maxRetries) {
4861
- try {
4862
- await new Promise((resolve2, reject) => {
4863
- const task = (0, import_storage3.uploadBytesResumable)(fileRef, data, {
4864
- customMetadata: metadata
4865
- });
4866
- task.on("state_changed", null, reject, resolve2);
4867
- });
4868
- return;
4869
- } catch (err) {
4870
- lastError = err;
4871
- attempt++;
4872
- if (attempt < maxRetries) {
4873
- await new Promise((res) => setTimeout(res, 1e3 * attempt));
4874
- }
4875
- }
6663
+ requestDocumentData(uuids) {
6664
+ if (this._destroyed)
6665
+ return;
6666
+ this.documents.requestDocumentData(uuids);
6667
+ }
6668
+ // ── Search ─────────────────────────────────────────────────────────────────
6669
+ /**
6670
+ * Run a natural-language search against the project.
6671
+ * The result is stored in `searchResults` and replaces any previous result.
6672
+ */
6673
+ async search(term, options) {
6674
+ if (this._destroyed)
6675
+ return;
6676
+ const result = await this._api.search({
6677
+ term,
6678
+ projectId: this._projectId,
6679
+ categories: options?.categories
6680
+ });
6681
+ if (!this._destroyed) {
6682
+ this._searchResults.set(result);
4876
6683
  }
4877
- throw lastError;
4878
6684
  }
6685
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
4879
6686
  /**
4880
- * Upload data to the processed storage bucket.
4881
- * 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.
4882
6690
  */
4883
- async uploadProcessed(blobName, data, metadata) {
4884
- const fileRef = (0, import_storage3.ref)(this.options.storageProcessed, blobName);
4885
- const existing = await (0, import_storage3.getMetadata)(fileRef).catch(() => null);
4886
- if (existing)
4887
- return false;
4888
- await (0, import_storage3.uploadBytes)(fileRef, data, { customMetadata: metadata });
4889
- return true;
6691
+ setLanguage(lang) {
6692
+ if (this._destroyed)
6693
+ return;
6694
+ this.schema.setLanguage(lang);
6695
+ this.documents.setLanguage(lang);
4890
6696
  }
4891
6697
  /**
4892
- * 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.
4893
6701
  */
4894
- async listRaw(prefix) {
4895
- const listRef = (0, import_storage3.ref)(this.options.storageRaw, prefix);
4896
- const result = await (0, import_storage3.listAll)(listRef);
4897
- 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));
4898
6709
  }
4899
6710
  /**
4900
- * 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.
4901
6713
  */
4902
- async downloadPublic(blobName) {
4903
- const fileRef = (0, import_storage3.ref)(this.options.storagePublic, blobName);
4904
- const controller = new AbortController();
4905
- const timeout = setTimeout(() => controller.abort(), 1e4);
4906
- try {
4907
- const [url, metadata] = await Promise.all([
4908
- (0, import_storage3.getDownloadURL)(fileRef),
4909
- (0, import_storage3.getMetadata)(fileRef)
4910
- ]);
4911
- const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
4912
- const res = await fetch(cacheBustedUrl, { signal: controller.signal });
4913
- if (!res.ok)
4914
- throw new Error(`HTTP ${res.status}`);
4915
- return res.text();
4916
- } catch (err) {
4917
- const isTimeout = err instanceof Error && err.name === "AbortError";
4918
- throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
4919
- } finally {
4920
- clearTimeout(timeout);
4921
- }
6714
+ destroy() {
6715
+ this._destroyed = true;
6716
+ this._searchResults.set(void 0);
4922
6717
  }
4923
6718
  };
4924
6719
 
4925
- // libs/js/cue-sdk/src/lib/sync.ts
4926
- var import_promises3 = require("fs/promises");
4927
- var import_path3 = require("path");
4928
- var import_url = require("url");
4929
- var import_os = require("os");
6720
+ // libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
6721
+ init_src();
4930
6722
 
4931
6723
  // libs/js/models/src/lib/file-extensions.ts
4932
6724
  var fileExtensionsInfo = {
@@ -5828,9 +7620,11 @@ var import_uuid5 = require("uuid");
5828
7620
 
5829
7621
  // libs/js/rdf-document-writers/src/lib/alternative-representation.ts
5830
7622
  var import_n33 = require("n3");
7623
+ init_src();
5831
7624
 
5832
7625
  // libs/js/rdf-document-writers/src/lib/file-location.ts
5833
7626
  var import_n32 = require("n3");
7627
+ init_src();
5834
7628
  var { namedNode, literal } = import_n32.DataFactory;
5835
7629
  var a = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
5836
7630
  var prefixes = {
@@ -5853,6 +7647,7 @@ var { namedNode: namedNode3, literal: literal2 } = import_n34.DataFactory;
5853
7647
  var a2 = namedNode3("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
5854
7648
 
5855
7649
  // libs/js/rdf-document-writers/src/lib/document-file.ts
7650
+ init_src();
5856
7651
  var import_n35 = require("n3");
5857
7652
 
5858
7653
  // libs/js/rdf-document-writers/src/lib/file-suffix.ts
@@ -5871,6 +7666,7 @@ var a3 = namedNode4("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
5871
7666
  // libs/js/rdf-document-writers/src/lib/process-logs.ts
5872
7667
  var import_n36 = require("n3");
5873
7668
  var import_uuid6 = require("uuid");
7669
+ init_src();
5874
7670
  var { namedNode: namedNode5, literal: literal4 } = import_n36.DataFactory;
5875
7671
  var a4 = namedNode5("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
5876
7672
  var prefixes3 = {
@@ -5911,29 +7707,59 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
5911
7707
  }
5912
7708
 
5913
7709
  // libs/js/cue-sdk/src/lib/sync.ts
7710
+ async function _readNodeFile(fullPath) {
7711
+ if (typeof window !== "undefined") {
7712
+ throw new Error(
7713
+ `Cannot read file from path "${fullPath}" in a browser environment. Provide file.data (Uint8Array) instead.`
7714
+ );
7715
+ }
7716
+ const { readFile } = await import("fs/promises");
7717
+ return readFile(fullPath);
7718
+ }
5914
7719
  var _scanFn = null;
5915
7720
  var _wasmInitPromise = null;
7721
+ var _browserWasmBaseUrl = null;
5916
7722
  async function _initWasm() {
5917
- const wasmDir = (0, import_path3.join)(__dirname, "assets", "wasm");
5918
- const wasmBinary = await (0, import_promises3.readFile)((0, import_path3.join)(wasmDir, "dir_scanner_wasm_bg.wasm"));
5919
- const glueUrl = (0, import_url.pathToFileURL)((0, import_path3.join)(wasmDir, "dir_scanner_wasm.mjs")).href;
5920
- const mod = await import(
5921
- /* @vite-ignore */
5922
- glueUrl
5923
- );
5924
- await mod.default({ module_or_path: wasmBinary });
5925
- _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
+ }
5926
7755
  }
5927
7756
  var DEFAULT_GRAPH_TYPE = "fuseki";
5928
- var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5929
- var ENDPOINT_QLEVER_QUERY = "/sparql/query";
5930
- var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5931
- var ENDPOINT_QLEVER_UPDATE = "/sparql/update";
5932
- var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
5933
7757
  var FSS_BATCH_CHUNK_SIZE = 1e3;
5934
7758
  var PENDING_LS_PREFIX = "cue:pending:";
5935
- function _pendingFilePath(spaceId) {
5936
- 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`);
5937
7763
  }
5938
7764
  async function _loadPending(spaceId) {
5939
7765
  if (typeof window !== "undefined") {
@@ -5941,7 +7767,7 @@ async function _loadPending(spaceId) {
5941
7767
  return raw ? JSON.parse(raw) : null;
5942
7768
  }
5943
7769
  try {
5944
- const raw = await (0, import_promises3.readFile)(_pendingFilePath(spaceId), "utf-8");
7770
+ const raw = await (await import("fs/promises")).readFile(await _pendingFilePath(spaceId), "utf-8");
5945
7771
  return JSON.parse(raw);
5946
7772
  } catch {
5947
7773
  return null;
@@ -5953,7 +7779,7 @@ async function _savePending(batch) {
5953
7779
  window.localStorage.setItem(`${PENDING_LS_PREFIX}${batch.spaceId}`, data);
5954
7780
  return;
5955
7781
  }
5956
- await (0, import_promises3.writeFile)(_pendingFilePath(batch.spaceId), data, "utf-8");
7782
+ await (await import("fs/promises")).writeFile(await _pendingFilePath(batch.spaceId), data, "utf-8");
5957
7783
  }
5958
7784
  async function _clearPending(spaceId) {
5959
7785
  if (typeof window !== "undefined") {
@@ -5961,7 +7787,7 @@ async function _clearPending(spaceId) {
5961
7787
  return;
5962
7788
  }
5963
7789
  try {
5964
- await (0, import_promises3.unlink)(_pendingFilePath(spaceId));
7790
+ await (await import("fs/promises")).unlink(await _pendingFilePath(spaceId));
5965
7791
  } catch {
5966
7792
  }
5967
7793
  }
@@ -5977,18 +7803,30 @@ var CueSyncApi = class {
5977
7803
  _pendingItems = [];
5978
7804
  _pendingSpaceId = null;
5979
7805
  _flushTimer = null;
7806
+ _legacy = false;
5980
7807
  /** @internal Injected by CueApi after construction to avoid circular dependency. */
5981
7808
  _bindApi(api) {
5982
7809
  this._api = api;
5983
7810
  }
5984
7811
  /**
5985
- * Flushes any pending metadata items from a previous interrupted sync.
5986
- * 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.
5987
7819
  */
5988
- async flushPendingMetadata(spaceId, verbose) {
5989
- const token = await this._auth.getToken();
5990
- if (!token)
5991
- 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;
5992
7830
  const existing = await _loadPending(spaceId);
5993
7831
  if (!existing || existing.items.length === 0)
5994
7832
  return;
@@ -5998,7 +7836,7 @@ var CueSyncApi = class {
5998
7836
  try {
5999
7837
  this._pendingSpaceId = spaceId;
6000
7838
  this._pendingItems = [];
6001
- await this._flushBatch(existing.items, spaceId, token, verbose);
7839
+ await this._flushBatch(existing.items, spaceId, verbose);
6002
7840
  console.info("Metadata uploaded \u2705");
6003
7841
  } catch (err) {
6004
7842
  throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -6019,7 +7857,7 @@ var CueSyncApi = class {
6019
7857
  const tier = project?.projectSettings?.tier ?? "l";
6020
7858
  const [remoteFiles, consumption, creditMap, tierNames] = await Promise.all([
6021
7859
  this._listRemoteFiles(graph, spaceId, providerId, verbose),
6022
- this._api?.getConsumption(spaceId, 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")),
6023
7861
  this._fetchUnitCreditMap(verbose),
6024
7862
  this._fetchTierNames()
6025
7863
  ]);
@@ -6053,18 +7891,17 @@ var CueSyncApi = class {
6053
7891
  };
6054
7892
  }
6055
7893
  async sync(localFiles, options) {
6056
- const { spaceId, providerId, userId, verbose, onProgress } = options;
7894
+ const { spaceId, providerId, userId, verbose, onProgress, legacy } = options;
7895
+ this._legacy = legacy ?? false;
6057
7896
  const token = await this._auth.getToken();
6058
7897
  if (!token)
6059
7898
  throw new Error("Not authenticated. Call cue.auth.signIn() first.");
6060
7899
  const graph = await this._getOrCreateGraph(spaceId, token);
6061
7900
  if (verbose)
6062
7901
  console.info("Listing remote files \u23F3");
6063
- const project = await this._projects.getProject(spaceId);
6064
- const tier = project?.projectSettings?.tier ?? "l";
6065
7902
  const [remoteFiles, consumption] = await Promise.all([
6066
7903
  this._listRemoteFiles(graph, spaceId, providerId, verbose),
6067
- this._api?.getConsumption(spaceId, 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"))
6068
7905
  ]);
6069
7906
  const { unitsAvailable } = consumption;
6070
7907
  const report = await compareLocalRemote(localFiles, remoteFiles);
@@ -6089,7 +7926,7 @@ var CueSyncApi = class {
6089
7926
  );
6090
7927
  }
6091
7928
  }
6092
- await this._initPendingBatch(spaceId, token, verbose);
7929
+ await this._initPendingBatch(spaceId, verbose);
6093
7930
  if (verbose && toUpload.length)
6094
7931
  console.info("Syncing missing files \u23F3");
6095
7932
  for (const file of toUpload) {
@@ -6103,10 +7940,10 @@ var CueSyncApi = class {
6103
7940
  );
6104
7941
  if (!rawMeta.blob_name)
6105
7942
  throw new Error(`blob_name missing for ${file.relativePath}`);
6106
- const fileBuffer = await (0, import_promises3.readFile)(file.fullPath);
7943
+ const fileBuffer = file.data ?? new Uint8Array(await _readNodeFile(file.fullPath));
6107
7944
  await this._blob.uploadRaw(
6108
7945
  rawMeta.blob_name,
6109
- new Uint8Array(fileBuffer),
7946
+ fileBuffer,
6110
7947
  rawMeta
6111
7948
  );
6112
7949
  await this._queueFileLocation({
@@ -6144,7 +7981,7 @@ var CueSyncApi = class {
6144
7981
  }
6145
7982
  await this._drainPending(verbose);
6146
7983
  this._stopFlushTimer();
6147
- const postSyncConsumption = await (this._api?.getConsumption(spaceId, tier) ?? Promise.resolve({ creditsAvailable: 0 }));
7984
+ const postSyncConsumption = await (this._api?.getConsumption(spaceId) ?? Promise.resolve({ creditsAvailable: 0 }));
6148
7985
  return {
6149
7986
  syncCount,
6150
7987
  syncSize,
@@ -6237,7 +8074,11 @@ WHERE {
6237
8074
  }
6238
8075
  return map;
6239
8076
  }
6240
- async _initPendingBatch(spaceId, token, verbose) {
8077
+ async _initPendingBatch(spaceId, verbose) {
8078
+ if (this._flushTimer !== null) {
8079
+ clearInterval(this._flushTimer);
8080
+ this._flushTimer = null;
8081
+ }
6241
8082
  this._pendingSpaceId = spaceId;
6242
8083
  this._pendingItems = [];
6243
8084
  const existing = await _loadPending(spaceId);
@@ -6246,7 +8087,7 @@ WHERE {
6246
8087
  if (verbose)
6247
8088
  console.info(`Flushing ${existing.items.length} pending file location(s) from previous sync \u23F3`);
6248
8089
  try {
6249
- await this._flushBatch(existing.items, spaceId, token, verbose);
8090
+ await this._flushBatch(existing.items, spaceId, verbose);
6250
8091
  console.info("Metadata uploaded \u2705");
6251
8092
  } catch (err) {
6252
8093
  throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -6268,22 +8109,29 @@ WHERE {
6268
8109
  await _savePending({ spaceId: this._pendingSpaceId, items: this._pendingItems });
6269
8110
  }
6270
8111
  }
8112
+ /**
8113
+ * Flush all queued file-location items to the commands API in a single batch.
8114
+ * Call this once after a group of `syncBrowserFile` calls completes so that
8115
+ * all items are sent together rather than one POST per file.
8116
+ */
8117
+ async drainPending() {
8118
+ await this._drainPending();
8119
+ }
6271
8120
  async _drainPending(verbose) {
6272
8121
  if (!this._pendingSpaceId || this._pendingItems.length === 0)
6273
8122
  return;
6274
- const token = await this._auth.getToken();
6275
- if (!token)
8123
+ if (!this._auth.currentUser)
6276
8124
  return;
6277
- await this._flushBatch(this._pendingItems, this._pendingSpaceId, token, verbose);
8125
+ await this._flushBatch(this._pendingItems, this._pendingSpaceId, verbose);
6278
8126
  }
6279
- async _flushBatch(items, spaceId, token, verbose) {
8127
+ async _flushBatch(items, spaceId, verbose) {
6280
8128
  const snapshot = [...items];
6281
8129
  if (this._pendingSpaceId === spaceId)
6282
8130
  this._pendingItems = [];
6283
8131
  await _clearPending(spaceId);
6284
8132
  try {
6285
8133
  for (let i = 0; i < snapshot.length; i += FSS_BATCH_CHUNK_SIZE) {
6286
- await this._postFssBatch(snapshot.slice(i, i + FSS_BATCH_CHUNK_SIZE), spaceId, token);
8134
+ await this._postFssBatch(snapshot.slice(i, i + FSS_BATCH_CHUNK_SIZE), spaceId);
6287
8135
  }
6288
8136
  if (verbose)
6289
8137
  console.info(`Wrote ${snapshot.length} file location(s) to commands API \u2705`);
@@ -6294,26 +8142,20 @@ WHERE {
6294
8142
  throw err;
6295
8143
  }
6296
8144
  }
6297
- async _postFssBatch(items, spaceId, token) {
6298
- const controller = new AbortController();
6299
- const timeout = setTimeout(() => controller.abort(), 15e3);
8145
+ async _postFssBatch(items, spaceId) {
8146
+ const url = this._legacy ? `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}?blob=true` : `${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`;
6300
8147
  let response;
6301
8148
  try {
6302
- response = await fetch(`${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`, {
8149
+ response = await this._auth.authenticatedFetch(url, {
6303
8150
  method: "POST",
6304
8151
  headers: {
6305
- Authorization: `Bearer ${token}`,
6306
8152
  "Content-Type": "application/json",
6307
8153
  "x-project-id": spaceId
6308
8154
  },
6309
- body: JSON.stringify({ items }),
6310
- signal: controller.signal
8155
+ body: JSON.stringify({ items })
6311
8156
  });
6312
8157
  } catch (err) {
6313
- const isTimeout = err instanceof Error && err.name === "AbortError";
6314
- throw new Error(`File structure batch POST failed: ${isTimeout ? "request timed out" : err instanceof Error ? err.message : String(err)}`);
6315
- } finally {
6316
- clearTimeout(timeout);
8158
+ throw new Error(`File structure batch POST failed: ${err instanceof Error ? err.message : String(err)}`);
6317
8159
  }
6318
8160
  if (!response.ok) {
6319
8161
  const body = await response.text().catch(() => "");
@@ -6346,7 +8188,8 @@ WHERE {
6346
8188
  const entries = await Promise.all(
6347
8189
  batch.map(async (f) => ({
6348
8190
  originalPath: f.relativePath,
6349
- data: new Uint8Array(await (0, import_promises3.readFile)(f.fullPath))
8191
+ // Use pre-loaded data if available (browser), otherwise read from disk (Node.js).
8192
+ data: f.data ?? new Uint8Array(await _readNodeFile(f.fullPath))
6350
8193
  }))
6351
8194
  );
6352
8195
  const records = _scanFn(entries);
@@ -6363,6 +8206,94 @@ WHERE {
6363
8206
  }
6364
8207
  return Array.from(merged.values());
6365
8208
  }
8209
+ /**
8210
+ * Compute the credit cost for a set of local files without uploading anything.
8211
+ * Intended for browser use where the full {@link previewSync} (which requires a
8212
+ * remote file listing) would be too heavy for a quick estimate.
8213
+ *
8214
+ * @param localFiles - Files to analyse. Each entry must carry `data` when
8215
+ * called from a browser context.
8216
+ * @param spaceId - Project/space identifier used to fetch the tier settings.
8217
+ * @returns Per-extension cost breakdown and the number of credits currently
8218
+ * available in the project.
8219
+ */
8220
+ async computeCredits(localFiles, spaceId) {
8221
+ const project = await this._projects.getProject(spaceId);
8222
+ const tier = project?.projectSettings?.tier ?? "l";
8223
+ console.info(`Computing credit cost for ${localFiles.length} file(s) using tier "${tier}"...`);
8224
+ const consumptionPromise = (this._api?.getConsumption(spaceId) ?? Promise.resolve({ creditsAvailable: 0, unitsAvailable: 0 })).then((r) => {
8225
+ console.info("[computeCredits] getConsumption resolved:", r);
8226
+ return r;
8227
+ }).catch((err) => {
8228
+ console.warn("[computeCredits] getConsumption failed, defaulting to 0:", err?.message ?? err);
8229
+ return { creditsAvailable: 0, unitsAvailable: 0 };
8230
+ });
8231
+ const creditMapPromise = this._fetchUnitCreditMap().then((m) => {
8232
+ console.info("[computeCredits] creditMap resolved, keys:", Object.keys(m));
8233
+ return m;
8234
+ }).catch((err) => {
8235
+ console.warn("[computeCredits] fetchUnitCreditMap failed, using default rates:", err?.message ?? err);
8236
+ return {};
8237
+ });
8238
+ const scanPromise = localFiles.length > 0 ? (console.info(`[computeCredits] starting WASM scan of ${localFiles.length} file(s)...`), this.scanCost(localFiles).then((r) => {
8239
+ console.info(`[computeCredits] WASM scan done: ${r.length} ext(s)`);
8240
+ return r;
8241
+ })) : Promise.resolve([]);
8242
+ const [costRecords, creditMap, consumption] = await Promise.all([
8243
+ scanPromise,
8244
+ creditMapPromise,
8245
+ consumptionPromise
8246
+ ]);
8247
+ console.info(`[computeCredits] all resolved \u2014 ${costRecords.length} ext(s), creditsAvailable: ${consumption.creditsAvailable}`);
8248
+ let creditsToConsume = 0;
8249
+ for (const r of costRecords) {
8250
+ const tierMap = creditMap[tier];
8251
+ const creditPerUnit = tierMap?.[r.ext] ?? 1;
8252
+ const credits = r.units * creditPerUnit;
8253
+ creditsToConsume += credits;
8254
+ r.credits = Math.round(credits);
8255
+ }
8256
+ return {
8257
+ costRecords,
8258
+ creditsToConsume: Math.round(creditsToConsume),
8259
+ creditsAvailable: consumption.creditsAvailable
8260
+ };
8261
+ }
8262
+ /**
8263
+ * Upload a single browser-supplied file and write its metadata to the knowledge graph.
8264
+ *
8265
+ * Unlike {@link sync} (which performs a full remote comparison), this method is
8266
+ * designed for the web file-manager flow where the user has already confirmed the
8267
+ * upload via the credit modal. The file's binary data must be provided in
8268
+ * `file.data`; the `file.fullPath` field is ignored.
8269
+ *
8270
+ * Cancellation is supported via `options.signal`. Aborting the signal cancels
8271
+ * the Firebase Storage upload; metadata is never written for a cancelled upload.
8272
+ *
8273
+ * @param file - `LocalFile` with `data` populated (e.g. from `File.arrayBuffer()`).
8274
+ * @param options - Upload options including project/provider/user context and an
8275
+ * optional `AbortSignal` for cancellation and `onProgress` for tracking.
8276
+ */
8277
+ async syncBrowserFile(file, options) {
8278
+ const { spaceId, providerId, userId, signal, onProgress } = options;
8279
+ if (!file.data) {
8280
+ throw new Error("syncBrowserFile requires file.data (Uint8Array). Read the file with File.arrayBuffer() first.");
8281
+ }
8282
+ const rawMeta = uploadedFileMetadata(file.relativePath, spaceId, userId, file.md5, providerId);
8283
+ if (!rawMeta.blob_name)
8284
+ throw new Error(`blob_name missing for ${file.relativePath}`);
8285
+ await this._blob.uploadRaw(
8286
+ rawMeta.blob_name,
8287
+ file.data,
8288
+ rawMeta,
8289
+ 3,
8290
+ signal,
8291
+ onProgress
8292
+ );
8293
+ await this._queueFileLocation(
8294
+ { relativePath: file.relativePath, md5: file.md5, size: file.size, providerId, fileContentExists: false }
8295
+ );
8296
+ }
6366
8297
  async _fetchTierNames() {
6367
8298
  try {
6368
8299
  const text = await this._blob.downloadPublic("tier-names.json");
@@ -6394,28 +8325,201 @@ WHERE {
6394
8325
  }
6395
8326
  };
6396
8327
 
8328
+ // libs/js/cue-sdk/src/lib/cue.ts
8329
+ var ENDPOINTS = {
8330
+ production: {
8331
+ gatewayUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app",
8332
+ tokenUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token",
8333
+ authEmulatorUrl: "http://localhost:9099",
8334
+ storageEmulatorHost: "localhost",
8335
+ storageEmulatorPort: 9199,
8336
+ firestoreEmulatorHost: "localhost",
8337
+ firestoreEmulatorPort: 8080
8338
+ },
8339
+ emulator: {
8340
+ gatewayUrl: "http://localhost:18093",
8341
+ tokenUrl: "http://localhost:18093/token",
8342
+ authEmulatorUrl: "http://localhost:9099",
8343
+ storageEmulatorHost: "localhost",
8344
+ storageEmulatorPort: 9199,
8345
+ firestoreEmulatorHost: "localhost",
8346
+ firestoreEmulatorPort: 8080
8347
+ }
8348
+ };
8349
+ var Cue = class _Cue {
8350
+ auth;
8351
+ api;
8352
+ projects;
8353
+ profile;
8354
+ privileges;
8355
+ cache;
8356
+ _app;
8357
+ _endpoints;
8358
+ _isEmulator;
8359
+ constructor(config = {}) {
8360
+ const usingDefaults = !config.apiKey && !config.appId && !config.measurementId;
8361
+ if (usingDefaults) {
8362
+ console.warn(
8363
+ "Using default SDK app settings. Contact QAECY for your own configuration for any production code."
8364
+ );
8365
+ }
8366
+ const apiKey = config.apiKey ?? DEFAULT_SDK_CONFIG.apiKey;
8367
+ const appId = config.appId ?? DEFAULT_SDK_CONFIG.appId;
8368
+ const measurementId = config.measurementId ?? DEFAULT_SDK_CONFIG.measurementId;
8369
+ const env = config.environment ?? "production";
8370
+ this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
8371
+ this._isEmulator = env === "emulator";
8372
+ this._app = (0, import_app2.getApps)().find((a5) => a5.name === "[DEFAULT]") ?? (0, import_app2.initializeApp)({
8373
+ apiKey,
8374
+ appId,
8375
+ measurementId,
8376
+ authDomain: `${FIREBASE_PROJECT_ID}.firebaseapp.com`,
8377
+ projectId: FIREBASE_PROJECT_ID,
8378
+ messagingSenderId: FIREBASE_SENDER_ID
8379
+ });
8380
+ this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
8381
+ this.projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
8382
+ this.api = this._buildApi(this.projects);
8383
+ this.profile = new CueProfile(
8384
+ this.auth,
8385
+ this._app,
8386
+ this._isEmulator,
8387
+ this._endpoints.firestoreEmulatorHost,
8388
+ this._endpoints.firestoreEmulatorPort
8389
+ );
8390
+ this.privileges = new CuePrivileges(this.auth.isSuperAdmin);
8391
+ const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
8392
+ if (this._isEmulator) {
8393
+ (0, import_storage5.connectStorageEmulator)(storagePersistence, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
8394
+ }
8395
+ this.cache = new CueCache(storagePersistence);
8396
+ }
8397
+ /**
8398
+ * Create a `Cue` instance from an already-initialized Firebase app.
8399
+ *
8400
+ * Use this when the host application (e.g. cue-portal via `CueFirebase`) has
8401
+ * already called `initializeApp()`. Reusing the same `FirebaseApp` means the
8402
+ * SDK shares the same auth state and Firestore instance — no second login is
8403
+ * needed and `getToken()` returns the portal user's token directly.
8404
+ *
8405
+ * Emulator connections are skipped because the host app already set them up.
8406
+ *
8407
+ * @example
8408
+ * // In an Angular service after CueFirebase is ready:
8409
+ * const cue = Cue.fromApp(CueFirebase.getInstance().app!, {
8410
+ * environment: 'emulator',
8411
+ * });
8412
+ */
8413
+ static fromApp(app, config = {}) {
8414
+ const env = config.environment ?? "production";
8415
+ const endpoints = { ...ENDPOINTS[env], ...config.endpoints };
8416
+ const auth = new CueAuth(app, false, endpoints);
8417
+ const projects = new CueProjects(auth, app, false, endpoints);
8418
+ const storageRaw = (0, import_storage5.getStorage)(app, BUCKET_RAW2);
8419
+ const storageProcessed = (0, import_storage5.getStorage)(app, BUCKET_PROCESSED2);
8420
+ const storagePublic = (0, import_storage5.getStorage)(app, BUCKET_PUBLIC2);
8421
+ const storageLogs = (0, import_storage5.getStorage)(app, BUCKET_LOGS2);
8422
+ const storageChatSessions = (0, import_storage5.getStorage)(app, BUCKET_CHAT_SESSIONS2);
8423
+ const storagePersistence = (0, import_storage5.getStorage)(app, BUCKET_PERSISTENCE2);
8424
+ const blob = new CueBlobStorage({
8425
+ storageRaw,
8426
+ storageProcessed,
8427
+ storagePublic,
8428
+ storageLogs,
8429
+ storageChatSessions,
8430
+ storagePersistence
8431
+ });
8432
+ const syncApi = new CueSyncApi(auth, projects, blob, endpoints.gatewayUrl);
8433
+ const api = new CueApi(auth, endpoints.gatewayUrl, projects, syncApi);
8434
+ syncApi._bindApi(api);
8435
+ const profile = new CueProfile(auth, app, false, endpoints.firestoreEmulatorHost, endpoints.firestoreEmulatorPort);
8436
+ const instance = Object.create(_Cue.prototype);
8437
+ const privileges = new CuePrivileges(auth.isSuperAdmin);
8438
+ const cache = new CueCache(storagePersistence);
8439
+ Object.assign(instance, {
8440
+ _app: app,
8441
+ _endpoints: endpoints,
8442
+ _isEmulator: env === "emulator",
8443
+ auth,
8444
+ api,
8445
+ projects,
8446
+ profile,
8447
+ privileges,
8448
+ cache
8449
+ });
8450
+ return instance;
8451
+ }
8452
+ /** Override in subclasses to provide a custom CueApi (e.g. with sync). */
8453
+ _buildApi(projects) {
8454
+ return new CueApi(this.auth, this._endpoints.gatewayUrl, projects);
8455
+ }
8456
+ /** Convenience: get the current user's Firebase ID token */
8457
+ getToken(forceRefresh = false) {
8458
+ return this.auth.getToken(forceRefresh);
8459
+ }
8460
+ /**
8461
+ * Create a `CueProjectView` for the given project, wiring the SDK's query
8462
+ * cache automatically.
8463
+ *
8464
+ * The view auto-fetches the document overview and entity graph on construction
8465
+ * and exposes reactive signals for all project knowledge-graph state.
8466
+ *
8467
+ * @example
8468
+ * ```ts
8469
+ * const view = cue.createProjectView('my-project', { language: 'en' });
8470
+ * view.requestEntityData(['uuid1']);
8471
+ * const info = view.entityInfoMap.get()['uuid1'];
8472
+ * ```
8473
+ */
8474
+ createProjectView(projectId, opts) {
8475
+ const queryCache = opts.queryCache ?? {
8476
+ get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
8477
+ set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
8478
+ };
8479
+ return new CueProjectView(this.api, projectId, { ...opts, queryCache });
8480
+ }
8481
+ };
8482
+
6397
8483
  // libs/js/cue-sdk/src/lib/cue-node.ts
6398
- var BUCKET_RAW2 = "spaces_raw_eu_west6";
6399
- var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
6400
- var BUCKET_PUBLIC2 = "cue_public_eu_west6";
8484
+ var import_storage6 = require("firebase/storage");
6401
8485
  var CueNode = class extends Cue {
6402
8486
  constructor(config) {
6403
8487
  super(config);
6404
8488
  }
6405
8489
  _buildApi(projects) {
6406
- const storageRaw = (0, import_storage4.getStorage)(this._app, BUCKET_RAW2);
6407
- const storageProcessed = (0, import_storage4.getStorage)(this._app, BUCKET_PROCESSED2);
6408
- const storagePublic = (0, import_storage4.getStorage)(this._app, BUCKET_PUBLIC2);
8490
+ const storageChatSessions = (0, import_storage6.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
8491
+ const storageLogs = (0, import_storage6.getStorage)(this._app, BUCKET_LOGS2);
8492
+ const storageRaw = (0, import_storage6.getStorage)(this._app, BUCKET_RAW2);
8493
+ const storagePersistence = (0, import_storage6.getStorage)(this._app, BUCKET_PERSISTENCE2);
8494
+ const storageProcessed = (0, import_storage6.getStorage)(this._app, BUCKET_PROCESSED2);
8495
+ const storagePublic = (0, import_storage6.getStorage)(this._app, BUCKET_PUBLIC2);
6409
8496
  if (this._isEmulator) {
6410
8497
  const storageHost = this._endpoints.storageEmulatorHost;
6411
8498
  const storagePort = this._endpoints.storageEmulatorPort;
6412
- (0, import_storage4.connectStorageEmulator)(storageRaw, storageHost, storagePort);
6413
- (0, import_storage4.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
6414
- (0, import_storage4.connectStorageEmulator)(storagePublic, storageHost, storagePort);
8499
+ (0, import_storage6.connectStorageEmulator)(storageRaw, storageHost, storagePort);
8500
+ (0, import_storage6.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
8501
+ (0, import_storage6.connectStorageEmulator)(storagePublic, storageHost, storagePort);
6415
8502
  }
6416
- const blob = new CueBlobStorage({ storageRaw, storageProcessed, storagePublic });
6417
- const syncApi = new CueSyncApi(this.auth, projects, blob, this._endpoints.gatewayUrl);
6418
- const api = new CueApi(this.auth, this._endpoints.gatewayUrl, projects, syncApi);
8503
+ const blob = new CueBlobStorage({
8504
+ storageChatSessions,
8505
+ storageLogs,
8506
+ storageRaw,
8507
+ storagePersistence,
8508
+ storageProcessed,
8509
+ storagePublic
8510
+ });
8511
+ const syncApi = new CueSyncApi(
8512
+ this.auth,
8513
+ projects,
8514
+ blob,
8515
+ this._endpoints.gatewayUrl
8516
+ );
8517
+ const api = new CueApi(
8518
+ this.auth,
8519
+ this._endpoints.gatewayUrl,
8520
+ projects,
8521
+ syncApi
8522
+ );
6419
8523
  syncApi._bindApi(api);
6420
8524
  return api;
6421
8525
  }
@@ -6457,7 +8561,7 @@ async function compareHandler(options) {
6457
8561
  await authenticate(emulators, space, options.key, verbose);
6458
8562
  if (verbose)
6459
8563
  console.info("Building compare base \u23F3");
6460
- const qh = async (query3) => queryHandler(query3, space, emulators);
8564
+ const qh = async (query4) => queryHandler(query4, space, emulators);
6461
8565
  const [localFiles, remoteFiles] = await Promise.all([
6462
8566
  listLocalFiles(
6463
8567
  path,
@@ -6557,23 +8661,23 @@ async function compareHandler(options) {
6557
8661
  }
6558
8662
 
6559
8663
  // apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
6560
- var import_storage5 = require("firebase/storage");
6561
- var import_path4 = require("path");
6562
- var import_promises4 = require("fs/promises");
8664
+ var import_storage7 = require("firebase/storage");
8665
+ var import_path2 = require("path");
8666
+ var import_promises = require("fs/promises");
6563
8667
  async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
6564
8668
  const firebase = CueFirebase.getInstance();
6565
8669
  const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
6566
8670
  console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
6567
- const listResult = await (0, import_storage5.listAll)((0, import_storage5.ref)(storage, subDir));
6568
- const outputDir = (0, import_path4.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
6569
- await (0, import_promises4.mkdir)(outputDir, { recursive: true });
8671
+ const listResult = await (0, import_storage7.listAll)((0, import_storage7.ref)(storage, subDir));
8672
+ const outputDir = (0, import_path2.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
8673
+ await (0, import_promises.mkdir)(outputDir, { recursive: true });
6570
8674
  for (const fileRef of listResult.items) {
6571
8675
  console.log(fileRef.name);
6572
8676
  if (subDir && !fileRef.name.includes(subString))
6573
8677
  continue;
6574
- const bytes = await (0, import_storage5.getBytes)(fileRef);
6575
- const outputPath = (0, import_path4.join)(outputDir, fileRef.name);
6576
- await (0, import_promises4.writeFile)(outputPath, Buffer.from(bytes));
8678
+ const bytes = await (0, import_storage7.getBytes)(fileRef);
8679
+ const outputPath = (0, import_path2.join)(outputDir, fileRef.name);
8680
+ await (0, import_promises.writeFile)(outputPath, Buffer.from(bytes));
6577
8681
  console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
6578
8682
  }
6579
8683
  }
@@ -6598,12 +8702,12 @@ async function dumpProcessedHandler(options) {
6598
8702
  }
6599
8703
 
6600
8704
  // apps/desktop/cue-cli/src/helpers/graph-dump.ts
6601
- var import_fs2 = require("fs");
8705
+ var import_fs = require("fs");
6602
8706
  var import_stream = require("stream");
6603
- var import_fs3 = require("fs");
6604
- var import_zlib2 = require("zlib");
6605
- var import_promises5 = require("stream/promises");
6606
- var import_promises6 = require("fs/promises");
8707
+ var import_fs2 = require("fs");
8708
+ var import_zlib = require("zlib");
8709
+ var import_promises2 = require("stream/promises");
8710
+ var import_promises3 = require("fs/promises");
6607
8711
  var import_auth9 = require("firebase/auth");
6608
8712
 
6609
8713
  // libs/js/size-tools/src/lib/js-size-tools.ts
@@ -6623,7 +8727,7 @@ function humanFileSize(bytes, si = false, dp = 1) {
6623
8727
  }
6624
8728
 
6625
8729
  // apps/desktop/cue-cli/src/helpers/graph-dump.ts
6626
- var import_fs4 = require("fs");
8730
+ var import_fs3 = require("fs");
6627
8731
  async function dumpRdfGraphToFileJelly(spaceId, useEmulator = false, verbose = false) {
6628
8732
  return dumpRdfGraphToFile(
6629
8733
  spaceId,
@@ -6639,7 +8743,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
6639
8743
  if (verbose)
6640
8744
  console.info(`Streaming RDF graph \u23F3`);
6641
8745
  const filePath = outFile || `${spaceId}.nq`;
6642
- if ((0, import_fs4.existsSync)(filePath) || (0, import_fs4.existsSync)(`${filePath}.gz`)) {
8746
+ if ((0, import_fs3.existsSync)(filePath) || (0, import_fs3.existsSync)(`${filePath}.gz`)) {
6643
8747
  if (verbose)
6644
8748
  console.info(`File ${filePath} already exists, skipping download.`);
6645
8749
  return mimeType === "application/n-quads" ? `${filePath}.gz` : filePath;
@@ -6656,7 +8760,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
6656
8760
  if (!res.ok || !res.body) {
6657
8761
  throw new Error(`Failed to stream data: ${res.status} ${res.statusText}`);
6658
8762
  }
6659
- const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
8763
+ const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
6660
8764
  const nodeStream = import_stream.Readable.fromWeb(res.body);
6661
8765
  let chunkCount = 0;
6662
8766
  let totalSize = 0;
@@ -6683,7 +8787,7 @@ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false,
6683
8787
  }
6684
8788
  }, streamTimeout);
6685
8789
  try {
6686
- await (0, import_promises5.pipeline)(nodeStream, fileStream);
8790
+ await (0, import_promises2.pipeline)(nodeStream, fileStream);
6687
8791
  } finally {
6688
8792
  if (hasDataTimeout) {
6689
8793
  clearTimeout(hasDataTimeout);
@@ -6716,7 +8820,7 @@ async function dumpRdfGraphToFileConstruct(spaceId, useEmulator = false, verbose
6716
8820
  WHERE { GRAPH ${graph} { ?s ?p ?o } }
6717
8821
  `;
6718
8822
  let offset = 0;
6719
- const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
8823
+ const fileStream = (0, import_fs.createWriteStream)(filePath, { flags: "w" });
6720
8824
  let hasMore = true;
6721
8825
  while (hasMore) {
6722
8826
  const queries = Array.from(
@@ -6781,14 +8885,14 @@ async function retryWithBackoff(fn, maxRetries, delayMs, label) {
6781
8885
  async function _doGzip(filePath) {
6782
8886
  const gzFilePath = `${filePath}.gz`;
6783
8887
  await new Promise((resolve2, reject) => {
6784
- const gzip = (0, import_zlib2.createGzip)();
6785
- const source = (0, import_fs3.createReadStream)(filePath);
6786
- const dest = (0, import_fs2.createWriteStream)(gzFilePath);
6787
- (0, import_promises5.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
8888
+ const gzip = (0, import_zlib.createGzip)();
8889
+ const source = (0, import_fs2.createReadStream)(filePath);
8890
+ const dest = (0, import_fs.createWriteStream)(gzFilePath);
8891
+ (0, import_promises2.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
6788
8892
  });
6789
- await (0, import_promises6.unlink)(filePath);
8893
+ await (0, import_promises3.unlink)(filePath);
6790
8894
  }
6791
- async function _doQuery(query3, spaceId, url) {
8895
+ async function _doQuery(query4, spaceId, url) {
6792
8896
  const token = await (0, import_auth9.getAuth)().currentUser?.getIdToken();
6793
8897
  try {
6794
8898
  const res = await fetch(url, {
@@ -6799,7 +8903,7 @@ async function _doQuery(query3, spaceId, url) {
6799
8903
  "Content-Type": "application/sparql-query",
6800
8904
  Accept: "application/n-quads"
6801
8905
  },
6802
- body: query3
8906
+ body: query4
6803
8907
  });
6804
8908
  if (!res.ok) {
6805
8909
  console.error(`Error: ${res.status} ${res.statusText}`);
@@ -6878,9 +8982,9 @@ function _getTemplate(partition, base = "fuseki-base-new/fuseki") {
6878
8982
 
6879
8983
  // apps/desktop/cue-cli/src/helpers/graph-upload.ts
6880
8984
  var import_auth11 = require("firebase/auth");
6881
- var import_fs5 = require("fs");
8985
+ var import_fs4 = require("fs");
6882
8986
  async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
6883
- const stream = (0, import_fs5.createReadStream)(filePath);
8987
+ const stream = (0, import_fs4.createReadStream)(filePath);
6884
8988
  const token = await (0, import_auth11.getAuth)().currentUser?.getIdToken();
6885
8989
  const dataUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", "/data");
6886
8990
  const headers = {
@@ -6907,7 +9011,7 @@ async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads"
6907
9011
 
6908
9012
  // apps/desktop/cue-cli/src/cue-cli-dump.ts
6909
9013
  async function dumpHandler(options) {
6910
- const { space, verbose, emulators, jelly, query: query3, load } = options;
9014
+ const { space, verbose, emulators, jelly, query: query4, load } = options;
6911
9015
  try {
6912
9016
  const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
6913
9017
  if (!isSuperAdmin) {
@@ -6928,7 +9032,7 @@ async function dumpHandler(options) {
6928
9032
  } else {
6929
9033
  if (verbose)
6930
9034
  console.time("Downloaded and zipped graph \u2705");
6931
- if (query3) {
9035
+ if (query4) {
6932
9036
  if (verbose)
6933
9037
  console.info("Setting: Construct query");
6934
9038
  filePath = await dumpRdfGraphToFileConstruct(space, emulators, verbose);
@@ -6965,16 +9069,16 @@ async function dumpHandler(options) {
6965
9069
  }
6966
9070
 
6967
9071
  // apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
6968
- var import_storage6 = require("firebase/storage");
9072
+ var import_storage8 = require("firebase/storage");
6969
9073
  async function repairRemoteTTL(space, subString, regex, substituteString) {
6970
9074
  const firebase = CueFirebase.getInstance();
6971
9075
  const storage = firebase.storageProcessed;
6972
9076
  console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
6973
- const listResult = await (0, import_storage6.listAll)((0, import_storage6.ref)(storage, `${space}/triples`));
9077
+ const listResult = await (0, import_storage8.listAll)((0, import_storage8.ref)(storage, `${space}/triples`));
6974
9078
  for (const fileRef of listResult.items) {
6975
9079
  if (subString && !fileRef.name.match(subString))
6976
9080
  continue;
6977
- const stream = await (0, import_storage6.getStream)(fileRef);
9081
+ const stream = await (0, import_storage8.getStream)(fileRef);
6978
9082
  const reader = stream.getReader();
6979
9083
  const chunks = [];
6980
9084
  let done = false;
@@ -6998,13 +9102,13 @@ async function repairRemoteTTL(space, subString, regex, substituteString) {
6998
9102
  const buffer = Buffer.from(fileContent, "utf8");
6999
9103
  let existingMetadata = {};
7000
9104
  try {
7001
- existingMetadata = await (0, import_storage6.getMetadata)(fileRef);
9105
+ existingMetadata = await (0, import_storage8.getMetadata)(fileRef);
7002
9106
  } catch (err) {
7003
9107
  console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
7004
9108
  }
7005
9109
  const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
7006
9110
  const metadata = { customMetadata };
7007
- await (0, import_storage6.uploadBytesResumable)((0, import_storage6.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
9111
+ await (0, import_storage8.uploadBytesResumable)((0, import_storage8.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
7008
9112
  console.log(`Fixed ${fileRef.name} \u2705`);
7009
9113
  } else {
7010
9114
  console.log(`No changes for ${fileRef.name}`);
@@ -7031,10 +9135,11 @@ async function repairTtlHandler(options) {
7031
9135
  }
7032
9136
 
7033
9137
  // apps/desktop/cue-cli/src/cue-cli-sync.ts
7034
- var import_promises7 = require("fs/promises");
7035
- var import_fs6 = require("fs");
7036
- var import_path5 = require("path");
9138
+ var import_promises4 = require("fs/promises");
9139
+ var import_fs5 = require("fs");
9140
+ var import_path3 = require("path");
7037
9141
  var readline = __toESM(require("readline"));
9142
+ init_src();
7038
9143
  function askConfirm(question) {
7039
9144
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7040
9145
  return new Promise((resolve2) => {
@@ -7045,7 +9150,7 @@ function askConfirm(question) {
7045
9150
  });
7046
9151
  }
7047
9152
  async function syncHandler(options) {
7048
- const { space, path, verbose, provider, emulators, zip } = options;
9153
+ const { space, path, verbose, provider, emulators, zip, legacy } = options;
7049
9154
  try {
7050
9155
  const cue = new CueNode({
7051
9156
  apiKey: FIREBASE_CONFIG().apiKey,
@@ -7063,15 +9168,15 @@ async function syncHandler(options) {
7063
9168
  }
7064
9169
  if (verbose)
7065
9170
  console.info("Building sync base \u23F3");
7066
- const resolvedPath = (0, import_path5.resolve)(path);
7067
- const pathStat = await (0, import_promises7.stat)(resolvedPath);
9171
+ const resolvedPath = (0, import_path3.resolve)(path);
9172
+ const pathStat = await (0, import_promises4.stat)(resolvedPath);
7068
9173
  const isFile = pathStat.isFile();
7069
9174
  let localFiles;
7070
9175
  if (isFile) {
7071
9176
  if (verbose)
7072
9177
  console.info(`Path is a file, syncing single file: ${resolvedPath}`);
7073
- const md5 = await fromReadStream((0, import_fs6.createReadStream)(resolvedPath));
7074
- const relativePath = (0, import_path5.basename)(resolvedPath);
9178
+ const md5 = await fromReadStream((0, import_fs5.createReadStream)(resolvedPath));
9179
+ const relativePath = (0, import_path3.basename)(resolvedPath);
7075
9180
  const contentUUID = contextBasedGuid(md5);
7076
9181
  const locationUUID = generateFileUUID(relativePath, provider);
7077
9182
  localFiles = [
@@ -7132,7 +9237,7 @@ async function syncHandler(options) {
7132
9237
  console.info(` Credits required: ${Math.round(preview.creditsToConsume)}`);
7133
9238
  console.info(` Credits available: ${Math.round(preview.creditsAvailable)}
7134
9239
  `);
7135
- await cue.api.sync.flushPendingMetadata(space, verbose);
9240
+ await cue.api.sync.flushPendingMetadata(space, verbose, legacy);
7136
9241
  if (preview.filesToUpload === 0) {
7137
9242
  console.info("Everything is already synced.");
7138
9243
  process.exit(0);
@@ -7153,6 +9258,7 @@ async function syncHandler(options) {
7153
9258
  providerId: provider,
7154
9259
  userId: user.uid,
7155
9260
  verbose,
9261
+ legacy,
7156
9262
  onProgress: ({ percent, syncCount, totalCount }) => {
7157
9263
  const filled = Math.round(percent / 5);
7158
9264
  const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
@@ -7194,8 +9300,8 @@ async function syncHandler(options) {
7194
9300
  }
7195
9301
 
7196
9302
  // apps/desktop/cue-cli/src/cue-cli-util-remove-rdf-star.ts
7197
- var import_fs7 = require("fs");
7198
- var import_zlib3 = require("zlib");
9303
+ var import_fs6 = require("fs");
9304
+ var import_zlib2 = require("zlib");
7199
9305
 
7200
9306
  // libs/js/rdf-tools/src/lib/nq-to-nt.ts
7201
9307
  var import_n37 = require("n3");
@@ -7242,8 +9348,8 @@ async function utilRemoveRdfStarHandler(options) {
7242
9348
  console.info(`Input: ${input} (${isGzipped ? "gzipped" : "plain"})`);
7243
9349
  if (verbose)
7244
9350
  console.info(`Output: ${output}`);
7245
- const fileStream = (0, import_fs7.createReadStream)(input);
7246
- const inputStream = isGzipped ? fileStream.pipe((0, import_zlib3.createGunzip)()) : fileStream;
9351
+ const fileStream = (0, import_fs6.createReadStream)(input);
9352
+ const inputStream = isGzipped ? fileStream.pipe((0, import_zlib2.createGunzip)()) : fileStream;
7247
9353
  let removed = 0;
7248
9354
  const cleanStream = removeRDFStar(inputStream, (count) => {
7249
9355
  removed = count;
@@ -7251,9 +9357,9 @@ async function utilRemoveRdfStarHandler(options) {
7251
9357
  console.info(`Removed RDF-star triples so far: ${count}`);
7252
9358
  }
7253
9359
  });
7254
- const writeStream = (0, import_fs7.createWriteStream)(output);
9360
+ const writeStream = (0, import_fs6.createWriteStream)(output);
7255
9361
  if (isGzipped) {
7256
- const gzip = (0, import_zlib3.createGzip)();
9362
+ const gzip = (0, import_zlib2.createGzip)();
7257
9363
  gzip.pipe(writeStream);
7258
9364
  for await (const chunk of cleanStream) {
7259
9365
  gzip.write(chunk);
@@ -7278,7 +9384,7 @@ async function utilRemoveRdfStarHandler(options) {
7278
9384
  }
7279
9385
 
7280
9386
  // apps/desktop/cue-cli/src/cue-cli-util-rdf-compare.ts
7281
- var import_fs8 = require("fs");
9387
+ var import_fs7 = require("fs");
7282
9388
 
7283
9389
  // libs/js/rdf-compare/src/lib/js-rdf-compare.ts
7284
9390
  var import_n39 = require("n3");
@@ -7347,8 +9453,8 @@ async function utilRdfCompareHandler(options) {
7347
9453
  console.info(`File 1: ${file1}`);
7348
9454
  if (verbose)
7349
9455
  console.info(`File 2: ${file2}`);
7350
- const content1 = (0, import_fs8.readFileSync)(file1, "utf8");
7351
- const content2 = (0, import_fs8.readFileSync)(file2, "utf8");
9456
+ const content1 = (0, import_fs7.readFileSync)(file1, "utf8");
9457
+ const content2 = (0, import_fs7.readFileSync)(file2, "utf8");
7352
9458
  const result = await compareTTL(content1, content2);
7353
9459
  console.info(`File 1 triple count: ${result.file1TripleCount}`);
7354
9460
  console.info(`File 2 triple count: ${result.file2TripleCount}`);
@@ -7376,10 +9482,10 @@ Triples only in file 2 (${result.triplesOnlyInFile2.size}):`);
7376
9482
  // apps/desktop/cue-cli/src/main.ts
7377
9483
  var packageJson;
7378
9484
  try {
7379
- packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "package.json"), "utf8"));
9485
+ packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "package.json"), "utf8"));
7380
9486
  } catch {
7381
9487
  try {
7382
- packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf8"));
9488
+ packageJson = JSON.parse((0, import_fs8.readFileSync)((0, import_path4.join)(__dirname, "../package.json"), "utf8"));
7383
9489
  } catch {
7384
9490
  packageJson = { version: "0.0.0" };
7385
9491
  console.warn("Could not find package.json, using fallback version");
@@ -7387,7 +9493,7 @@ try {
7387
9493
  }
7388
9494
  var program = new import_commander.Command();
7389
9495
  program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
7390
- program.command("sync").description("Sync files to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", 'Include zipped content (will be unzipped to path "<zip_path>_unzipped". Max uncompressed size: 500 MB, max recursion depth: 3)', false).action(syncHandler);
9496
+ program.command("sync").description("Sync files to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", 'Include zipped content (will be unzipped to path "<zip_path>_unzipped". Max uncompressed size: 500 MB, max recursion depth: 3)', false).option("--legacy", "Write RDF as BLOBs to the processed bucket instead of patching the graph directly", false).action(syncHandler);
7391
9497
  program.command("dump").description("Dump Cue Knowledge Graph data to file\n Examples:\n $ cue-cli dump -s <space_id> -l -v\n $ cue-cli dump -s <space_id> -j -v").requiredOption("-s, --space <id>", "Specify the space ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-q, --query", "Uses a construct query to get the dump rather than using the /data endpoint", false).option("-j, --jelly", "Downloads a Jelly file rather than the standard Gzipped NQuads format", false).option("-l, --load", "Loads the dumped file into a local triplestore (requires emulators)", false).action(dumpHandler);
7392
9498
  program.command("compare").description("Compares folder content to files already updated to Cue").requiredOption("-s, --space <id>", "Specify the space ID (required)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", "Include zipped content (will temporarily unzip files with same logic as when syncing and delete them again after the comparison)", false).action(compareHandler);
7393
9499
  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);