@qaecy/cue-cli 0.0.46 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.js CHANGED
@@ -372,7 +372,7 @@ var init_md5_builder_node = __esm({
372
372
  });
373
373
 
374
374
  // apps/desktop/cue-cli/src/main.ts
375
- var import_commander = require("commander");
375
+ var import_commander2 = require("commander");
376
376
  var import_fs8 = require("fs");
377
377
  var import_path4 = require("path");
378
378
 
@@ -4721,6 +4721,53 @@ var prefixCC = {
4721
4721
  rex: "http://purl.obolibrary.org/obo/REX_"
4722
4722
  };
4723
4723
 
4724
+ // libs/js/prefixes/src/lib/compact-expand.ts
4725
+ var CompactExpand = class _CompactExpand {
4726
+ static _instance;
4727
+ // Opposite map is built once
4728
+ _nsMap;
4729
+ static getInstance() {
4730
+ if (this._instance) {
4731
+ return this._instance;
4732
+ }
4733
+ this._instance = new _CompactExpand();
4734
+ return this._instance;
4735
+ }
4736
+ compactIRI(iri) {
4737
+ let nsMap = this._nsMap;
4738
+ if (nsMap === void 0)
4739
+ nsMap = this._buildNSMap();
4740
+ let ns = iri;
4741
+ if (iri.includes("#"))
4742
+ ns = `${iri.split("#")[0]}#`;
4743
+ else {
4744
+ const parts = iri.split("/");
4745
+ parts.pop();
4746
+ ns = parts.join("/") + "/";
4747
+ }
4748
+ if (ns === void 0)
4749
+ return iri;
4750
+ const pfx = nsMap[ns];
4751
+ if (pfx === void 0)
4752
+ return iri;
4753
+ return iri.replace(ns, `${pfx}:`);
4754
+ }
4755
+ expandIRI(iri) {
4756
+ const prefix = iri.split(":")[0];
4757
+ const ns = prefixCC[prefix] ?? qaecyPrefixes[prefix];
4758
+ return iri.replace(`${prefix}:`, ns);
4759
+ }
4760
+ _buildNSMap() {
4761
+ const map = {};
4762
+ Object.keys(prefixCC).forEach((key) => map[prefixCC[key]] = key);
4763
+ Object.keys(qaecyPrefixes).forEach(
4764
+ (key) => map[qaecyPrefixes[key]] = key
4765
+ );
4766
+ this._nsMap = map;
4767
+ return map;
4768
+ }
4769
+ };
4770
+
4724
4771
  // libs/js/sync-tools/src/lib/list-remote-files.ts
4725
4772
  async function listRemoteFiles(spaceId, providerId, queryHandler2, verbose = false) {
4726
4773
  const firebase = CueFirebase.getInstance();
@@ -5097,8 +5144,10 @@ var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
5097
5144
  var ENDPOINT_SEARCH = "/assistant/search";
5098
5145
  var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5099
5146
  var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5147
+ var ENDPOINT_FUSEKI_SHACL = "/triplestore/shacl";
5100
5148
  var ENDPOINT_QLEVER_QUERY = "/qlever-server/qlever/query";
5101
5149
  var ENDPOINT_QLEVER_UPDATE = "/qlever-server/qlever/update";
5150
+ var ENDPOINT_QLEVER_SHACL = "/qlever-server/qlever/shacl";
5102
5151
  var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
5103
5152
  var MICROSOFT_PROVIDER_ID = "microsoft.com";
5104
5153
  var SUPERADMIN_ROLE = "superadmin";
@@ -5290,6 +5339,89 @@ var CueAuth = class {
5290
5339
  }
5291
5340
  };
5292
5341
 
5342
+ // libs/js/cue-sdk/src/lib/storage.ts
5343
+ var CueStorage = class {
5344
+ constructor(_blob) {
5345
+ this._blob = _blob;
5346
+ }
5347
+ /**
5348
+ * Returns a Firebase authenticated download URL for a document stored in Cue.
5349
+ *
5350
+ * The storage path is `{projectId}/{uuid}{suffix}`, e.g. `my-project/abc-123.pdf`.
5351
+ *
5352
+ * @param projectId - The Cue project (space) ID.
5353
+ * @param uuid - The document UUID.
5354
+ * @param suffix - File suffix including the leading dot, e.g. `'.pdf'`, `'.ifc'`.
5355
+ * @param bucket - `'raw'` (default, original uploads) or `'processed'` (derived artefacts).
5356
+ */
5357
+ async getDownloadUrl(projectId, uuid, suffix, bucket = "raw") {
5358
+ const path = `${projectId}/${uuid}${suffix}`;
5359
+ const url = await this._blob.getDownloadURL(bucket, path);
5360
+ if (!url)
5361
+ throw new Error(`File not found in storage: ${path} (bucket: ${bucket})`);
5362
+ return url;
5363
+ }
5364
+ /**
5365
+ * Returns a Firebase authenticated download URL for an alternative representation
5366
+ * using its full `qcy:remoteRelativePath` stored in the processed bucket.
5367
+ *
5368
+ * Use this instead of `getDownloadUrl` when the document info was obtained via
5369
+ * `fetchAlternativeRepresentations` and carries a `remoteRelativePath`.
5370
+ *
5371
+ * @param remoteRelativePath - The full path in the processed bucket,
5372
+ * e.g. `{projectId}/fragments/{uuid}.fragments`.
5373
+ */
5374
+ async getAltRepDownloadUrl(remoteRelativePath) {
5375
+ const url = await this._blob.getDownloadURL("processed", remoteRelativePath);
5376
+ if (!url)
5377
+ throw new Error(`Alternative representation not found in storage: ${remoteRelativePath}`);
5378
+ return url;
5379
+ }
5380
+ };
5381
+
5382
+ // libs/js/cue-sdk/src/lib/tables.ts
5383
+ var ENDPOINT_DATA_VIEWS_TABLES = "/data-views/tables";
5384
+ var ENDPOINT_COMMANDS_TABLES = "/commands/tables";
5385
+ var CueTables = class {
5386
+ constructor(_auth, _gatewayUrl) {
5387
+ this._auth = _auth;
5388
+ this._gatewayUrl = _gatewayUrl;
5389
+ }
5390
+ async listTables(projectId) {
5391
+ const response = await this._auth.authenticatedFetch(
5392
+ `${this._gatewayUrl}${ENDPOINT_DATA_VIEWS_TABLES}`,
5393
+ {
5394
+ headers: {
5395
+ "Content-Type": "application/json",
5396
+ "x-project-id": projectId,
5397
+ "cue-project-id": projectId
5398
+ }
5399
+ }
5400
+ );
5401
+ if (!response.ok) {
5402
+ throw new Error(`Failed to list tables: ${response.status} ${response.statusText}`);
5403
+ }
5404
+ return response.json();
5405
+ }
5406
+ async saveTables(tables, projectId) {
5407
+ const response = await this._auth.authenticatedFetch(
5408
+ `${this._gatewayUrl}${ENDPOINT_COMMANDS_TABLES}`,
5409
+ {
5410
+ method: "PUT",
5411
+ headers: {
5412
+ "Content-Type": "application/json",
5413
+ "x-project-id": projectId,
5414
+ "cue-project-id": projectId
5415
+ },
5416
+ body: JSON.stringify(tables)
5417
+ }
5418
+ );
5419
+ if (!response.ok) {
5420
+ throw new Error(`Failed to save tables: ${response.status} ${response.statusText}`);
5421
+ }
5422
+ }
5423
+ };
5424
+
5293
5425
  // libs/js/cue-sdk/src/lib/api.ts
5294
5426
  var CueApi = class {
5295
5427
  constructor(_auth, _gatewayUrl, projects, sync) {
@@ -5297,6 +5429,14 @@ var CueApi = class {
5297
5429
  this._gatewayUrl = _gatewayUrl;
5298
5430
  this.projects = projects;
5299
5431
  this.sync = sync;
5432
+ this.tables = new CueTables(_auth, _gatewayUrl);
5433
+ }
5434
+ tables;
5435
+ /** Active language used for language-sensitive SPARQL queries across all project classes. */
5436
+ language = "en";
5437
+ /** Updates the active language. All project classes (`CueProjectSchema`, `CueProjectDocuments`, `CueProjectEntities`) read this at query time. */
5438
+ setLanguage(lang) {
5439
+ this.language = lang;
5300
5440
  }
5301
5441
  /**
5302
5442
  * Returns standard authentication headers for the current user.
@@ -5368,6 +5508,57 @@ var CueApi = class {
5368
5508
  }
5369
5509
  return response.json();
5370
5510
  }
5511
+ /**
5512
+ * Validate a SHACL shape against the project's triplestore.
5513
+ *
5514
+ * @param shape - SHACL shapes graph in Turtle syntax.
5515
+ * @param projectId - Project to validate against.
5516
+ * @param options - `format`: `'json-ld'` (default, structured result) or `'turtle'` (raw string).
5517
+ * `verbose`: include server-side timing logs.
5518
+ *
5519
+ * Returns a {@link ShaclValidationReport} for JSON-LD, or a Turtle string.
5520
+ */
5521
+ async shacl(shape, projectId, options) {
5522
+ const fmt = options?.format ?? "json-ld";
5523
+ const accept = fmt === "turtle" ? "text/turtle" : "application/ld+json";
5524
+ if (options?.graphType === "fuseki") {
5525
+ const response2 = await this._auth.authenticatedFetch(
5526
+ `${this._gatewayUrl}${ENDPOINT_FUSEKI_SHACL}`,
5527
+ {
5528
+ method: "POST",
5529
+ headers: {
5530
+ "Content-Type": "text/turtle",
5531
+ "Accept": accept,
5532
+ "x-project-id": projectId,
5533
+ "cue-project-id": projectId
5534
+ },
5535
+ body: shape
5536
+ }
5537
+ );
5538
+ if (!response2.ok) {
5539
+ const msg = await response2.text().catch(() => "");
5540
+ throw new Error(`SHACL validation failed: ${response2.status} ${response2.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5541
+ }
5542
+ return fmt === "turtle" ? response2.text() : response2.json();
5543
+ }
5544
+ const url = `${this._gatewayUrl}${ENDPOINT_QLEVER_SHACL}${options?.verbose ? "?verbose=true" : ""}`;
5545
+ const body = new URLSearchParams({ shape });
5546
+ const response = await this._auth.authenticatedFetch(url, {
5547
+ method: "POST",
5548
+ headers: {
5549
+ "Content-Type": "application/x-www-form-urlencoded",
5550
+ "Accept": accept,
5551
+ "x-project-id": projectId,
5552
+ "cue-project-id": projectId
5553
+ },
5554
+ body
5555
+ });
5556
+ if (!response.ok) {
5557
+ const msg = await response.text().catch(() => "");
5558
+ throw new Error(`SHACL validation failed: ${response.status} ${response.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5559
+ }
5560
+ return fmt === "turtle" ? response.text() : response.json();
5561
+ }
5371
5562
  async getConsumption(projectId) {
5372
5563
  const response = await this._auth.authenticatedFetch(
5373
5564
  `${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`,
@@ -7552,19 +7743,20 @@ var CueProjectSchema = class {
7552
7743
  this._projectId = _projectId;
7553
7744
  this._queryCache = _queryCache;
7554
7745
  this._graphType = _graphType;
7555
- this._language = new CueSignal(language);
7746
+ this._currentLang = language;
7747
+ this._api.setLanguage(language);
7556
7748
  this._contentCategories = new CueSignal([]);
7557
7749
  this._entityCategories = new CueSignal([]);
7558
7750
  this._relationships = new CueSignal([]);
7559
7751
  this.availableContentCategories = this._contentCategories.asReadonly();
7560
7752
  this.availableEntityCategories = this._entityCategories.asReadonly();
7561
7753
  this.availableEntityRelationships = this._relationships.asReadonly();
7562
- this._load(language).catch(
7754
+ this.ready = this._load(language).catch(
7563
7755
  (err) => console.error("[CueProjectSchema] Initial load failed:", err)
7564
7756
  );
7565
7757
  }
7566
7758
  _cache = /* @__PURE__ */ new Map();
7567
- _language;
7759
+ _currentLang;
7568
7760
  _contentCategories;
7569
7761
  _entityCategories;
7570
7762
  _relationships;
@@ -7574,9 +7766,14 @@ var CueProjectSchema = class {
7574
7766
  availableEntityCategories;
7575
7767
  /** Currently active entity relationship types for the selected language. */
7576
7768
  availableEntityRelationships;
7769
+ /**
7770
+ * Resolves when the initial schema load for the constructor language has
7771
+ * completed (or failed). Await this before reading signal values imperatively.
7772
+ */
7773
+ ready;
7577
7774
  /** Returns the currently active language. */
7578
7775
  get language() {
7579
- return this._language.get();
7776
+ return this._api.language;
7580
7777
  }
7581
7778
  /**
7582
7779
  * Switch the active language. If the data for this language has already been
@@ -7584,9 +7781,10 @@ var CueProjectSchema = class {
7584
7781
  * is triggered.
7585
7782
  */
7586
7783
  setLanguage(lang) {
7587
- if (this._language.get() === lang)
7784
+ if (this._currentLang === lang)
7588
7785
  return;
7589
- this._language.set(lang);
7786
+ this._currentLang = lang;
7787
+ this._api.setLanguage(lang);
7590
7788
  this._load(lang).catch(
7591
7789
  (err) => console.error("[CueProjectSchema] Language switch failed:", err)
7592
7790
  );
@@ -7596,7 +7794,7 @@ var CueProjectSchema = class {
7596
7794
  * Useful when the triplestore data has changed.
7597
7795
  */
7598
7796
  async refresh() {
7599
- const lang = this._language.get();
7797
+ const lang = this._api.language;
7600
7798
  this._cache.delete(lang);
7601
7799
  await this._load(lang);
7602
7800
  }
@@ -7622,7 +7820,7 @@ var CueProjectSchema = class {
7622
7820
  },
7623
7821
  (snapshot) => {
7624
7822
  this._cache.set(lang, snapshot);
7625
- if (this._language.get() === lang)
7823
+ if (this._currentLang === lang)
7626
7824
  this._apply(snapshot);
7627
7825
  },
7628
7826
  this._queryCache
@@ -7711,7 +7909,9 @@ var CueProjectEntities = class {
7711
7909
  baseURL;
7712
7910
  // ── Internal writable slices ───────────────────────────────────────────────
7713
7911
  _entityDetails = new CueSignal({});
7714
- _entityDocuments = new CueSignal({});
7912
+ _entityDocuments = new CueSignal(
7913
+ {}
7914
+ );
7715
7915
  _entityRelationships = new CueSignal({});
7716
7916
  _entityOSMMap = new CueSignal({});
7717
7917
  _osmWKTMap = new CueSignal({});
@@ -7753,7 +7953,10 @@ var CueProjectEntities = class {
7753
7953
  this._entityGraph.set(void 0);
7754
7954
  this._fetchingOSMIds.clear();
7755
7955
  this._fetchEntityGraph().catch(
7756
- (err) => console.error("[CueProjectEntities] Entity graph fetch failed after reset:", err)
7956
+ (err) => console.error(
7957
+ "[CueProjectEntities] Entity graph fetch failed after reset:",
7958
+ err
7959
+ )
7757
7960
  );
7758
7961
  }
7759
7962
  // ── Public imperative API ──────────────────────────────────────────────────
@@ -7764,7 +7967,9 @@ var CueProjectEntities = class {
7764
7967
  * Data is merged into `entityInfoMap` once the SPARQL response arrives.
7765
7968
  */
7766
7969
  requestEntityData(uuids, includeMentionCount = false) {
7767
- const newUUIDs = uuids.filter((id) => this._entityDetails.get()[id] === void 0);
7970
+ const newUUIDs = uuids.filter(
7971
+ (id) => this._entityDetails.get()[id] === void 0
7972
+ );
7768
7973
  if (newUUIDs.length === 0)
7769
7974
  return;
7770
7975
  const values = newUUIDs.map((id) => `r:${id}`).join(" ");
@@ -7782,7 +7987,9 @@ WHERE {
7782
7987
  GROUP BY ?id ?mentionCount`;
7783
7988
  this._api.sparql(q, this._projectId, this._graphType).then((data) => {
7784
7989
  const result = data;
7785
- const updates = { ...this._entityDetails.get() };
7990
+ const updates = {
7991
+ ...this._entityDetails.get()
7992
+ };
7786
7993
  result.results.bindings.forEach((b) => {
7787
7994
  if (!b["id"])
7788
7995
  return;
@@ -7806,7 +8013,9 @@ GROUP BY ?id ?mentionCount`;
7806
8013
  * query and merged into `entityInfoMap` reactively once it arrives.
7807
8014
  */
7808
8015
  async requestEntityLocations(uuids) {
7809
- const newUUIDs = uuids.filter((id) => this._entityOSMMap.get()[id] === void 0);
8016
+ const newUUIDs = uuids.filter(
8017
+ (id) => this._entityOSMMap.get()[id] === void 0
8018
+ );
7810
8019
  if (newUUIDs.length === 0)
7811
8020
  return;
7812
8021
  const osmInit = { ...this._entityOSMMap.get() };
@@ -7842,7 +8051,11 @@ WHERE {
7842
8051
  }
7843
8052
  BIND(REPLACE(STR(?iri), "^.*/([^/]*)$", "$1") AS ?id)
7844
8053
  }`;
7845
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8054
+ const data = await this._api.sparql(
8055
+ q,
8056
+ this._projectId,
8057
+ this._graphType
8058
+ );
7846
8059
  const update = { ...this._entityOSMMap.get() };
7847
8060
  data.results.bindings.forEach((b) => {
7848
8061
  if (!b["id"] || !b["osm"])
@@ -7881,7 +8094,10 @@ WHERE {
7881
8094
  this._fetchIncomingRelationships(iri)
7882
8095
  ]);
7883
8096
  const result = { outgoing, incoming };
7884
- this._entityRelationships.set({ ...this._entityRelationships.get(), [uuid]: result });
8097
+ this._entityRelationships.set({
8098
+ ...this._entityRelationships.get(),
8099
+ [uuid]: result
8100
+ });
7885
8101
  return result;
7886
8102
  }
7887
8103
  /**
@@ -7905,11 +8121,111 @@ WHERE {
7905
8121
  qcy:about ?iri .
7906
8122
  BIND(REPLACE(STR(?doc), "^.*/([^/]*)$", "$1") AS ?id)
7907
8123
  }`;
7908
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8124
+ const data = await this._api.sparql(
8125
+ q,
8126
+ this._projectId,
8127
+ this._graphType
8128
+ );
7909
8129
  const ids = data.results.bindings.filter((b) => b["id"] !== void 0).map((b) => b["id"].value);
7910
8130
  this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: ids });
7911
8131
  return ids;
7912
8132
  }
8133
+ /**
8134
+ * Fetches all `qcy:EntityCategory` IRIs and their preferred labels for this
8135
+ * project. Uses `api.language` (default `'en'`);
8136
+ * falls back to an untagged label when no match is found.
8137
+ */
8138
+ async contentCategoriesInProject(orderByOccurences = true) {
8139
+ const language = this._api.language;
8140
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8141
+ PREFIX skos: <${prefixCC["skos"]}>
8142
+ SELECT ?iri (SAMPLE(?l) AS ?label) ?count
8143
+ WHERE {
8144
+ ?iri a qcy:EntityCategory .
8145
+ OPTIONAL { ?iri skos:prefLabel ?lang_label FILTER(LANG(?lang_label) = "${language}") }
8146
+ OPTIONAL { ?iri skos:prefLabel ?no_lang_label }
8147
+ BIND(COALESCE(?lang_label, ?no_lang_label) AS ?l)
8148
+ ${orderByOccurences ? "{ SELECT (COUNT(?x) AS ?count) ?iri where { ?x a qcy:CanonicalEntity ; qcy:hasEntityCategory ?iri } GROUP BY ?iri }" : ""}
8149
+ }
8150
+ GROUP BY ?iri ?count
8151
+ ORDER BY ${orderByOccurences ? "DESC(?count)" : "ASC(?label)"}`;
8152
+ const data = await this._api.sparql(
8153
+ q,
8154
+ this._projectId,
8155
+ this._graphType
8156
+ );
8157
+ return data.results.bindings.filter((b) => b["iri"] !== void 0).map((b) => {
8158
+ const count = b["count"] ? parseInt(b["count"].value, 10) : 0;
8159
+ const label = b["label"]?.value ?? b["iri"].value.split("#").at(-1);
8160
+ const labelWithCount = count > 0 ? `${label} (${count})` : label ?? "";
8161
+ return {
8162
+ iri: b["iri"].value,
8163
+ label: labelWithCount
8164
+ };
8165
+ });
8166
+ }
8167
+ async buildSummaryGraph(format) {
8168
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8169
+ SELECT
8170
+ ?sourceCat
8171
+ ?predicate
8172
+ ?targetCat
8173
+ (COUNT(*) AS ?weight)
8174
+ WHERE {
8175
+ ?s a qcy:CanonicalEntity ;
8176
+ qcy:hasEntityCategory ?sourceCat .
8177
+ ?o a qcy:CanonicalEntity ;
8178
+ qcy:hasEntityCategory ?targetCat .
8179
+ ?s ?predicate ?o .
8180
+ FILTER(isIRI(?s) && isIRI(?o))
8181
+ FILTER(?predicate != qcy:relatedEntity)
8182
+ }
8183
+ GROUP BY ?sourceCat ?predicate ?targetCat
8184
+ ORDER BY DESC(?weight)`;
8185
+ const data = await this._api.sparql(
8186
+ q,
8187
+ this._projectId,
8188
+ this._graphType
8189
+ );
8190
+ const bindings = data.results.bindings.filter(
8191
+ (b) => b["sourceCat"] && b["predicate"] && b["targetCat"]
8192
+ );
8193
+ if (format === "graph") {
8194
+ const irisSet = /* @__PURE__ */ new Set();
8195
+ const relations = [];
8196
+ for (const b of bindings) {
8197
+ const sourceID = b["sourceCat"].value;
8198
+ const predicate = b["predicate"].value;
8199
+ const targetID = b["targetCat"].value;
8200
+ const weight = parseInt(b["weight"].value, 10);
8201
+ irisSet.add(sourceID);
8202
+ irisSet.add(targetID);
8203
+ relations.push({ sourceID, predicate, targetID, weight });
8204
+ }
8205
+ return {
8206
+ entities: Array.from(irisSet).map((iri) => ({ iri })),
8207
+ relations
8208
+ };
8209
+ }
8210
+ if (format === "md") {
8211
+ const ce = CompactExpand.getInstance();
8212
+ const rows = bindings.map((b) => ({
8213
+ src: ce.compactIRI(b["sourceCat"].value),
8214
+ pred: ce.compactIRI(b["predicate"].value),
8215
+ tgt: ce.compactIRI(b["targetCat"].value),
8216
+ weight: parseInt(b["weight"].value, 10)
8217
+ }));
8218
+ if (rows.length === 0)
8219
+ return "(no results)";
8220
+ const maxSrc = Math.max(...rows.map((r) => r.src.length));
8221
+ const maxPred = Math.max(...rows.map((r) => r.pred.length));
8222
+ const maxTgt = Math.max(...rows.map((r) => r.tgt.length));
8223
+ return rows.map(
8224
+ (r) => `${r.src.padEnd(maxSrc)} -> ${r.pred.padEnd(maxPred)} -> ${r.tgt.padEnd(maxTgt)} (${r.weight})`
8225
+ ).join("\n");
8226
+ }
8227
+ return data;
8228
+ }
7913
8229
  // ── Private helpers ────────────────────────────────────────────────────────
7914
8230
  _computeEntityInfoMap() {
7915
8231
  const details = this._entityDetails.get();
@@ -7983,7 +8299,11 @@ WHERE {
7983
8299
  FILTER(?rel != qcy:relatedEntity)
7984
8300
  }
7985
8301
  GROUP BY ?rel ?related`;
7986
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8302
+ const data = await this._api.sparql(
8303
+ q,
8304
+ this._projectId,
8305
+ this._graphType
8306
+ );
7987
8307
  const result = [];
7988
8308
  const detailsUpdate = { ...this._entityDetails.get() };
7989
8309
  data.results.bindings.forEach((b) => {
@@ -8017,7 +8337,11 @@ WHERE {
8017
8337
  FILTER(?rel != qcy:relatedEntity)
8018
8338
  }
8019
8339
  GROUP BY ?rel ?relating`;
8020
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8340
+ const data = await this._api.sparql(
8341
+ q,
8342
+ this._projectId,
8343
+ this._graphType
8344
+ );
8021
8345
  const result = [];
8022
8346
  const detailsUpdate = { ...this._entityDetails.get() };
8023
8347
  data.results.bindings.forEach((b) => {
@@ -8052,7 +8376,11 @@ GROUP BY ?e1Cat ?e2Cat`;
8052
8376
  await staleWhileRevalidate(
8053
8377
  q,
8054
8378
  async () => {
8055
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8379
+ const data = await this._api.sparql(
8380
+ q,
8381
+ this._projectId,
8382
+ this._graphType
8383
+ );
8056
8384
  const entities = [];
8057
8385
  const relations = [];
8058
8386
  data.results.bindings.forEach((b) => {
@@ -8109,7 +8437,11 @@ SELECT * WHERE {
8109
8437
  ?s geo:hasGeometry/geo:asWKT ?wkt
8110
8438
  }
8111
8439
  }`;
8112
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8440
+ const data = await this._api.sparql(
8441
+ q,
8442
+ this._projectId,
8443
+ this._graphType
8444
+ );
8113
8445
  const update = { ...this._osmWKTMap.get() };
8114
8446
  data.results.bindings.forEach((b) => {
8115
8447
  if (!b["s"] || !b["wkt"])
@@ -8129,13 +8461,14 @@ var CueProjectDocuments = class {
8129
8461
  this._queryCache = _queryCache;
8130
8462
  this._graphType = _graphType;
8131
8463
  this.baseURL = `${rdfBase}${_projectId}/`;
8132
- this._language = language;
8464
+ this._currentLang = language ?? this._api.language;
8133
8465
  this.documentInfoMap = this._documentInfoMap.asReadonly();
8134
8466
  this.projectDocumentsData = this._projectDocumentsData.asReadonly();
8135
8467
  }
8136
8468
  /** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
8137
8469
  baseURL;
8138
- _language;
8470
+ /** Tracks the language for which `_documentInfoMap` is currently populated. */
8471
+ _currentLang;
8139
8472
  _documentInfoMap = new CueSignal({});
8140
8473
  _projectDocumentsData = new CueSignal({
8141
8474
  duplicateCount: 0,
@@ -8165,9 +8498,10 @@ var CueProjectDocuments = class {
8165
8498
  * `requestDocumentData()` call.
8166
8499
  */
8167
8500
  setLanguage(lang) {
8168
- if (this._language === lang)
8501
+ if (this._currentLang === lang)
8169
8502
  return;
8170
- this._language = lang;
8503
+ this._currentLang = lang;
8504
+ this._api.setLanguage(lang);
8171
8505
  this._documentInfoMap.set({});
8172
8506
  }
8173
8507
  // ── Public API ─────────────────────────────────────────────────────────────
@@ -8208,8 +8542,128 @@ var CueProjectDocuments = class {
8208
8542
  const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
8209
8543
  if (newUUIDs.length === 0)
8210
8544
  return;
8211
- const values = newUUIDs.map((id) => `r:${id}`).join(" ");
8212
- const lang = this._language;
8545
+ this._fetchDocumentInfoBatch(newUUIDs).catch(
8546
+ (err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
8547
+ );
8548
+ }
8549
+ /**
8550
+ * Promise-based alternative to {@link requestDocumentData} for non-reactive contexts.
8551
+ *
8552
+ * Resolves with the `DocumentInfo` entries for every requested UUID once the
8553
+ * SPARQL response arrives. UUIDs already present in the cache are returned
8554
+ * immediately without a network request. The result is also written into
8555
+ * `documentInfoMap` so reactive consumers stay in sync.
8556
+ *
8557
+ * UUIDs not found in the triplestore are omitted from the returned map.
8558
+ *
8559
+ * @example
8560
+ * ```ts
8561
+ * const docs = await cueProjectDocs.fetchDocumentData(['uuid1', 'uuid2']);
8562
+ * console.log(docs['uuid1'].subject);
8563
+ * ```
8564
+ */
8565
+ async fetchDocumentData(uuids) {
8566
+ const current = this._documentInfoMap.get();
8567
+ const newUUIDs = uuids.filter((id) => current[id] === void 0);
8568
+ if (newUUIDs.length > 0) {
8569
+ await this._fetchDocumentInfoBatch(newUUIDs);
8570
+ }
8571
+ const updated = this._documentInfoMap.get();
8572
+ return Object.fromEntries(
8573
+ uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
8574
+ );
8575
+ }
8576
+ /**
8577
+ * Fetches a lightweight document metadata shape (id/path/suffix/size) for
8578
+ * the given UUIDs and merges the results into `documentInfoMap`.
8579
+ *
8580
+ * This is useful for list/table contexts that do not need language-tagged
8581
+ * fields (`subject`, `summary`) or category/tag enrichment.
8582
+ *
8583
+ * UUIDs already present in `documentInfoMap` are skipped.
8584
+ */
8585
+ async fetchDocumentDataSimple(uuids) {
8586
+ const current = this._documentInfoMap.get();
8587
+ const newUUIDs = uuids.filter((id) => current[id] === void 0);
8588
+ if (newUUIDs.length > 0) {
8589
+ await this._fetchSimpleDocumentInfoBatch(newUUIDs);
8590
+ }
8591
+ const updated = this._documentInfoMap.get();
8592
+ return Object.fromEntries(
8593
+ uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
8594
+ );
8595
+ }
8596
+ /**
8597
+ * Returns the alternative representations of the given document UUID.
8598
+ *
8599
+ * Alternative representations are derived artefacts stored under
8600
+ * `qcy:alternativeRepresentation` in the triplestore — for example a
8601
+ * `.fragments` BIM tile derived from an `.ifc` source file.
8602
+ *
8603
+ * The returned `DocumentInfo` entries are also merged into
8604
+ * `documentInfoMap` so reactive consumers stay in sync.
8605
+ *
8606
+ * @example
8607
+ * ```ts
8608
+ * const alts = await docs.fetchAlternativeRepresentations('abc-123');
8609
+ * // alts[0].suffix => '.fragments'
8610
+ * ```
8611
+ */
8612
+ async fetchAlternativeRepresentations(uuid) {
8613
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8614
+ PREFIX r: <${this.baseURL}>
8615
+ SELECT ?altId ?contentIRI ?suffix ?rrp
8616
+ WHERE {
8617
+ r:${uuid} qcy:alternativeRepresentation ?contentIRI .
8618
+ BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?altId)
8619
+ ?contentIRI qcy:hasFileLocation ?loc .
8620
+ ?loc qcy:suffix ?suffix .
8621
+ OPTIONAL { ?loc qcy:remoteRelativePath ?rrp }
8622
+ }`;
8623
+ const data = await this._api.sparql(q, this._projectId, this._graphType);
8624
+ const updates = { ...this._documentInfoMap.get() };
8625
+ const alts = [];
8626
+ data.results.bindings.forEach((b) => {
8627
+ if (!b["altId"] || !b["contentIRI"])
8628
+ return;
8629
+ const id = b["altId"].value;
8630
+ const info = {
8631
+ id,
8632
+ contentIRI: b["contentIRI"].value,
8633
+ path: "",
8634
+ suffix: b["suffix"]?.value ?? "",
8635
+ size: 0,
8636
+ tags: [],
8637
+ categories: [],
8638
+ remoteRelativePath: b["rrp"]?.value
8639
+ };
8640
+ updates[id] = info;
8641
+ alts.push(info);
8642
+ });
8643
+ this._documentInfoMap.set(updates);
8644
+ return alts;
8645
+ }
8646
+ /**
8647
+ * Returns a single arbitrary file path from the project's triplestore.
8648
+ * Useful for pre-filling path-based query inputs with a realistic example.
8649
+ */
8650
+ async randomFilePath() {
8651
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8652
+ SELECT ?path
8653
+ WHERE {
8654
+ ?fl a qcy:FileLocation ;
8655
+ qcy:filePath ?path .
8656
+ }
8657
+ LIMIT 1`;
8658
+ const data = await this._api.sparql(q, this._projectId, this._graphType);
8659
+ return data.results.bindings[0]?.["path"]?.value ?? null;
8660
+ }
8661
+ // ── Private helpers ────────────────────────────────────────────────────────
8662
+ /** Executes the document-info SPARQL query for the given UUIDs, merges results
8663
+ * into `documentInfoMap`, and returns the newly fetched entries. */
8664
+ async _fetchDocumentInfoBatch(uuids) {
8665
+ const values = uuids.map((id) => `r:${id}`).join(" ");
8666
+ const lang = this._api.language;
8213
8667
  const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8214
8668
  PREFIX r: <${this.baseURL}>
8215
8669
  SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
@@ -8237,32 +8691,73 @@ WHERE {
8237
8691
  BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
8238
8692
  }
8239
8693
  GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
8240
- this._api.sparql(q, this._projectId).then((data) => {
8241
- const result = data;
8242
- const updates = { ...this._documentInfoMap.get() };
8243
- result.results.bindings.forEach((b) => {
8244
- if (!b["id"] || !b["contentIRI"])
8245
- return;
8246
- const id = b["id"].value;
8247
- updates[id] = {
8248
- id,
8249
- contentIRI: b["contentIRI"].value,
8250
- path: b["path"]?.value ?? "",
8251
- suffix: b["suffix"]?.value ?? "",
8252
- size: b["size"] ? parseInt(b["size"].value, 10) : 0,
8253
- tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
8254
- categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
8255
- subject: b["subject"]?.value,
8256
- summary: b["summary"]?.value,
8257
- providerId: b["pid"]?.value
8258
- };
8259
- });
8260
- this._documentInfoMap.set(updates);
8261
- }).catch(
8262
- (err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
8263
- );
8694
+ const result = await this._api.sparql(q, this._projectId);
8695
+ const updates = { ...this._documentInfoMap.get() };
8696
+ const fetched = {};
8697
+ result.results.bindings.forEach((b) => {
8698
+ if (!b["id"] || !b["contentIRI"])
8699
+ return;
8700
+ const id = b["id"].value;
8701
+ const info = {
8702
+ id,
8703
+ contentIRI: b["contentIRI"].value,
8704
+ path: b["path"]?.value ?? "",
8705
+ suffix: b["suffix"]?.value ?? "",
8706
+ size: b["size"] ? parseInt(b["size"].value, 10) : 0,
8707
+ tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
8708
+ categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
8709
+ subject: b["subject"]?.value,
8710
+ summary: b["summary"]?.value,
8711
+ providerId: b["pid"]?.value
8712
+ };
8713
+ updates[id] = info;
8714
+ fetched[id] = info;
8715
+ });
8716
+ this._documentInfoMap.set(updates);
8717
+ return fetched;
8718
+ }
8719
+ /** Executes a reduced document-info query (id/path/suffix/size only), merges
8720
+ * into `documentInfoMap`, and returns newly fetched entries. */
8721
+ async _fetchSimpleDocumentInfoBatch(uuids) {
8722
+ const values = uuids.map((id) => `r:${id}`).join(" ");
8723
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8724
+ PREFIX r: <${this.baseURL}>
8725
+ SELECT ?id ?contentIRI ?suffix ?size (SAMPLE(?fp) AS ?path)
8726
+ WHERE {
8727
+ VALUES ?contentIRI { ${values} }
8728
+ ?contentIRI qcy:sizeBytes ?size ;
8729
+ qcy:hasFileLocation ?loc .
8730
+ ?loc qcy:filePath ?fp ;
8731
+ qcy:suffix ?suffix .
8732
+ BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
8733
+ }
8734
+ GROUP BY ?id ?contentIRI ?suffix ?size`;
8735
+ const result = await this._api.sparql(q, this._projectId, this._graphType);
8736
+ const updates = { ...this._documentInfoMap.get() };
8737
+ const fetched = {};
8738
+ result.results.bindings.forEach((b) => {
8739
+ if (!b["id"])
8740
+ return;
8741
+ const id = b["id"].value;
8742
+ const existing = updates[id];
8743
+ const info = {
8744
+ id,
8745
+ contentIRI: b["contentIRI"]?.value ?? existing?.contentIRI ?? id,
8746
+ path: b["path"]?.value ?? existing?.path ?? id,
8747
+ suffix: b["suffix"]?.value ?? existing?.suffix ?? "",
8748
+ size: b["size"] ? parseInt(b["size"].value, 10) : existing?.size ?? 0,
8749
+ tags: existing?.tags ?? [],
8750
+ categories: existing?.categories ?? [],
8751
+ subject: existing?.subject,
8752
+ summary: existing?.summary,
8753
+ providerId: existing?.providerId
8754
+ };
8755
+ updates[id] = info;
8756
+ fetched[id] = info;
8757
+ });
8758
+ this._documentInfoMap.set(updates);
8759
+ return fetched;
8264
8760
  }
8265
- // ── Private helpers ────────────────────────────────────────────────────────
8266
8761
  async _fetchDocumentsBySuffix() {
8267
8762
  return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
8268
8763
  }
@@ -8364,6 +8859,7 @@ var CueProjectView = class {
8364
8859
  this.availableContentCategories = this.schema.availableContentCategories;
8365
8860
  this.availableEntityCategories = this.schema.availableEntityCategories;
8366
8861
  this.availableEntityRelationships = this.schema.availableEntityRelationships;
8862
+ this.schemaReady = this.schema.ready;
8367
8863
  this.entityInfoMap = this.entities.entityInfoMap;
8368
8864
  this.entityGraph = this.entities.entityGraph;
8369
8865
  this.documentInfoMap = this.documents.documentInfoMap;
@@ -8384,6 +8880,11 @@ var CueProjectView = class {
8384
8880
  availableEntityCategories;
8385
8881
  /** Available entity relationship types. Auto-fetched on init. */
8386
8882
  availableEntityRelationships;
8883
+ /**
8884
+ * Resolves when the initial schema load has completed. Await before reading
8885
+ * schema signal values imperatively.
8886
+ */
8887
+ schemaReady;
8387
8888
  /** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
8388
8889
  entityInfoMap;
8389
8890
  /** Project-level entity co-occurrence graph. Fetched once on init. */
@@ -10191,10 +10692,14 @@ var Cue = class _Cue {
10191
10692
  profile;
10192
10693
  privileges;
10193
10694
  cache;
10695
+ storage;
10194
10696
  _app;
10195
10697
  _endpoints;
10196
10698
  _isEmulator;
10699
+ _storageRaw;
10700
+ _storageProcessed;
10197
10701
  _gis = null;
10702
+ _projectDocuments = /* @__PURE__ */ new Map();
10198
10703
  /**
10199
10704
  * Reactive GIS service. Lazily constructed on first access.
10200
10705
  *
@@ -10238,7 +10743,26 @@ var Cue = class _Cue {
10238
10743
  });
10239
10744
  this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
10240
10745
  this.projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
10746
+ this._storageRaw = (0, import_storage5.getStorage)(this._app, BUCKET_RAW2);
10747
+ this._storageProcessed = (0, import_storage5.getStorage)(this._app, BUCKET_PROCESSED2);
10748
+ const storagePublic = (0, import_storage5.getStorage)(this._app, BUCKET_PUBLIC2);
10749
+ const storageLogs = (0, import_storage5.getStorage)(this._app, BUCKET_LOGS2);
10750
+ const storageChatSessions = (0, import_storage5.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
10751
+ const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
10752
+ if (this._isEmulator) {
10753
+ (0, import_storage5.connectStorageEmulator)(this._storageRaw, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
10754
+ (0, import_storage5.connectStorageEmulator)(this._storageProcessed, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
10755
+ }
10241
10756
  this.api = this._buildApi(this.projects);
10757
+ const blob = new CueBlobStorage({
10758
+ storageRaw: this._storageRaw,
10759
+ storageProcessed: this._storageProcessed,
10760
+ storagePublic,
10761
+ storageLogs,
10762
+ storageChatSessions,
10763
+ storagePersistence
10764
+ });
10765
+ this.storage = new CueStorage(blob);
10242
10766
  this.profile = new CueProfile(
10243
10767
  this.auth,
10244
10768
  this._app,
@@ -10246,7 +10770,6 @@ var Cue = class _Cue {
10246
10770
  this._endpoints.gatewayUrl
10247
10771
  );
10248
10772
  this.privileges = new CuePrivileges(this.auth.isSuperAdmin);
10249
- const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
10250
10773
  if (this._isEmulator) {
10251
10774
  (0, import_storage5.connectStorageEmulator)(storagePersistence, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
10252
10775
  }
@@ -10294,16 +10817,20 @@ var Cue = class _Cue {
10294
10817
  const instance = Object.create(_Cue.prototype);
10295
10818
  const privileges = new CuePrivileges(auth.isSuperAdmin);
10296
10819
  const cache = new CueCache(storagePersistence);
10820
+ const storage = new CueStorage(blob);
10297
10821
  Object.assign(instance, {
10298
10822
  _app: app,
10299
10823
  _endpoints: endpoints,
10300
10824
  _isEmulator: env === "emulator",
10825
+ _storageRaw: storageRaw,
10826
+ _storageProcessed: storageProcessed,
10301
10827
  auth,
10302
10828
  api,
10303
10829
  projects,
10304
10830
  profile,
10305
10831
  privileges,
10306
- cache
10832
+ cache,
10833
+ storage
10307
10834
  });
10308
10835
  return instance;
10309
10836
  }
@@ -10336,27 +10863,105 @@ var Cue = class _Cue {
10336
10863
  };
10337
10864
  return new CueProjectView(this.api, projectId, { ...opts, queryCache });
10338
10865
  }
10866
+ /**
10867
+ * Creates a `CueProjectEntities` instance for the given project, with the
10868
+ * SDK query cache wired automatically.
10869
+ *
10870
+ * Prefer this over `new CueProjectEntities(cue.api, projectId)` — it avoids
10871
+ * the manual cache setup and keeps the instance bound to the correct API.
10872
+ *
10873
+ * @example
10874
+ * ```ts
10875
+ * const entities = cue.createProjectEntities('my-project');
10876
+ * entities.requestEntityData(['uuid1']);
10877
+ * const info = entities.entityInfoMap.get()['uuid1'];
10878
+ *
10879
+ * // Promise-based (no signal polling needed):
10880
+ * const graph = await entities.buildSummaryGraph('graph');
10881
+ * ```
10882
+ */
10883
+ createProjectEntities(projectId, opts) {
10884
+ const queryCache = opts?.queryCache ?? {
10885
+ get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
10886
+ set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
10887
+ };
10888
+ return new CueProjectEntities(
10889
+ this.api,
10890
+ projectId,
10891
+ opts?.rdfBase,
10892
+ queryCache,
10893
+ opts?.graphType
10894
+ );
10895
+ }
10896
+ /**
10897
+ * Creates a `CueProjectDocuments` instance for the given project, with the
10898
+ * SDK query cache wired automatically.
10899
+ *
10900
+ * Prefer this over `new CueProjectDocuments(cue.api, projectId)` — it avoids
10901
+ * the manual cache setup and keeps the instance bound to the correct API.
10902
+ *
10903
+ * @example
10904
+ * ```ts
10905
+ * const docs = cue.createProjectDocuments('my-project');
10906
+ * await docs.fetchOverview();
10907
+ * const data = await docs.fetchDocumentData(['uuid1', 'uuid2']);
10908
+ * ```
10909
+ */
10910
+ createProjectDocuments(projectId, opts) {
10911
+ const hasCustomOptions = opts?.language !== void 0 || opts?.rdfBase !== void 0 || opts?.graphType !== void 0 || opts?.queryCache !== void 0;
10912
+ if (!hasCustomOptions) {
10913
+ const existing = this._projectDocuments.get(projectId);
10914
+ if (existing) {
10915
+ existing.setLanguage(this.api.language);
10916
+ return existing;
10917
+ }
10918
+ const queryCache2 = {
10919
+ get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
10920
+ set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
10921
+ };
10922
+ const docs = new CueProjectDocuments(
10923
+ this.api,
10924
+ projectId,
10925
+ this.api.language,
10926
+ void 0,
10927
+ queryCache2,
10928
+ void 0
10929
+ );
10930
+ this._projectDocuments.set(projectId, docs);
10931
+ return docs;
10932
+ }
10933
+ const queryCache = opts?.queryCache ?? {
10934
+ get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
10935
+ set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
10936
+ };
10937
+ return new CueProjectDocuments(
10938
+ this.api,
10939
+ projectId,
10940
+ opts?.language ?? this.api.language,
10941
+ opts?.rdfBase,
10942
+ queryCache,
10943
+ opts?.graphType
10944
+ );
10945
+ }
10339
10946
  };
10340
10947
 
10341
10948
  // libs/js/cue-sdk/src/lib/cue-node.ts
10342
- var import_storage6 = require("firebase/storage");
10949
+ var import_storage8 = require("firebase/storage");
10343
10950
  var CueNode = class extends Cue {
10344
10951
  constructor(config) {
10345
10952
  super(config);
10346
10953
  }
10347
10954
  _buildApi(projects) {
10348
- const storageChatSessions = (0, import_storage6.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
10349
- const storageLogs = (0, import_storage6.getStorage)(this._app, BUCKET_LOGS2);
10350
- const storageRaw = (0, import_storage6.getStorage)(this._app, BUCKET_RAW2);
10351
- const storagePersistence = (0, import_storage6.getStorage)(this._app, BUCKET_PERSISTENCE2);
10352
- const storageProcessed = (0, import_storage6.getStorage)(this._app, BUCKET_PROCESSED2);
10353
- const storagePublic = (0, import_storage6.getStorage)(this._app, BUCKET_PUBLIC2);
10955
+ const storageChatSessions = (0, import_storage8.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
10956
+ const storageLogs = (0, import_storage8.getStorage)(this._app, BUCKET_LOGS2);
10957
+ const storageRaw = this._storageRaw;
10958
+ const storagePersistence = (0, import_storage8.getStorage)(this._app, BUCKET_PERSISTENCE2);
10959
+ const storageProcessed = this._storageProcessed;
10960
+ const storagePublic = (0, import_storage8.getStorage)(this._app, BUCKET_PUBLIC2);
10354
10961
  if (this._isEmulator) {
10355
10962
  const storageHost = this._endpoints.storageEmulatorHost;
10356
10963
  const storagePort = this._endpoints.storageEmulatorPort;
10357
- (0, import_storage6.connectStorageEmulator)(storageRaw, storageHost, storagePort);
10358
- (0, import_storage6.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
10359
- (0, import_storage6.connectStorageEmulator)(storagePublic, storageHost, storagePort);
10964
+ (0, import_storage8.connectStorageEmulator)(storagePublic, storageHost, storagePort);
10360
10965
  }
10361
10966
  const blob = new CueBlobStorage({
10362
10967
  storageChatSessions,
@@ -10519,21 +11124,21 @@ async function compareHandler(options) {
10519
11124
  }
10520
11125
 
10521
11126
  // apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
10522
- var import_storage7 = require("firebase/storage");
11127
+ var import_storage9 = require("firebase/storage");
10523
11128
  var import_path2 = require("path");
10524
11129
  var import_promises = require("fs/promises");
10525
11130
  async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
10526
11131
  const firebase = CueFirebase.getInstance();
10527
11132
  const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
10528
11133
  console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
10529
- const listResult = await (0, import_storage7.listAll)((0, import_storage7.ref)(storage, subDir));
11134
+ const listResult = await (0, import_storage9.listAll)((0, import_storage9.ref)(storage, subDir));
10530
11135
  const outputDir = (0, import_path2.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
10531
11136
  await (0, import_promises.mkdir)(outputDir, { recursive: true });
10532
11137
  for (const fileRef of listResult.items) {
10533
11138
  console.log(fileRef.name);
10534
11139
  if (subDir && !fileRef.name.includes(subString))
10535
11140
  continue;
10536
- const bytes = await (0, import_storage7.getBytes)(fileRef);
11141
+ const bytes = await (0, import_storage9.getBytes)(fileRef);
10537
11142
  const outputPath = (0, import_path2.join)(outputDir, fileRef.name);
10538
11143
  await (0, import_promises.writeFile)(outputPath, Buffer.from(bytes));
10539
11144
  console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
@@ -10927,16 +11532,16 @@ async function dumpHandler(options) {
10927
11532
  }
10928
11533
 
10929
11534
  // apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
10930
- var import_storage8 = require("firebase/storage");
11535
+ var import_storage10 = require("firebase/storage");
10931
11536
  async function repairRemoteTTL(space, subString, regex, substituteString) {
10932
11537
  const firebase = CueFirebase.getInstance();
10933
11538
  const storage = firebase.storageProcessed;
10934
11539
  console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
10935
- const listResult = await (0, import_storage8.listAll)((0, import_storage8.ref)(storage, `${space}/triples`));
11540
+ const listResult = await (0, import_storage10.listAll)((0, import_storage10.ref)(storage, `${space}/triples`));
10936
11541
  for (const fileRef of listResult.items) {
10937
11542
  if (subString && !fileRef.name.match(subString))
10938
11543
  continue;
10939
- const stream = await (0, import_storage8.getStream)(fileRef);
11544
+ const stream = await (0, import_storage10.getStream)(fileRef);
10940
11545
  const reader = stream.getReader();
10941
11546
  const chunks = [];
10942
11547
  let done = false;
@@ -10960,13 +11565,13 @@ async function repairRemoteTTL(space, subString, regex, substituteString) {
10960
11565
  const buffer = Buffer.from(fileContent, "utf8");
10961
11566
  let existingMetadata = {};
10962
11567
  try {
10963
- existingMetadata = await (0, import_storage8.getMetadata)(fileRef);
11568
+ existingMetadata = await (0, import_storage10.getMetadata)(fileRef);
10964
11569
  } catch (err) {
10965
11570
  console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
10966
11571
  }
10967
11572
  const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
10968
11573
  const metadata = { customMetadata };
10969
- await (0, import_storage8.uploadBytesResumable)((0, import_storage8.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
11574
+ await (0, import_storage10.uploadBytesResumable)((0, import_storage10.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
10970
11575
  console.log(`Fixed ${fileRef.name} \u2705`);
10971
11576
  } else {
10972
11577
  console.log(`No changes for ${fileRef.name}`);
@@ -11201,10 +11806,17 @@ async function syncHandler(options) {
11201
11806
  process.exit(0);
11202
11807
  } catch (err) {
11203
11808
  const msg = err instanceof Error ? err.message : String(err);
11809
+ const cause = err instanceof Error ? err.cause : void 0;
11810
+ const causeCode = cause instanceof Error ? cause.code : void 0;
11811
+ const causeMsg = cause instanceof Error ? cause.message : void 0;
11812
+ const isTimeout = causeCode === "UND_ERR_HEADERS_TIMEOUT" || causeCode === "UND_ERR_BODY_TIMEOUT" || causeCode === "UND_ERR_CONNECT_TIMEOUT" || causeMsg?.includes("Timeout") || msg.includes("fetch failed");
11204
11813
  if (msg.includes("GRAPH_UNAVAILABLE")) {
11205
11814
  console.error("The knowledge graph for this project has no database configured. Contact your administrator or check the project setup.");
11206
- } else if (msg.includes("GRAPH_TIMEOUT")) {
11207
- console.error("Could not reach the knowledge graph. Make sure all required services are running.");
11815
+ } else if (msg.includes("GRAPH_TIMEOUT") || isTimeout) {
11816
+ console.error("Could not reach the QAECY API \u2014 the request timed out or the server could not be reached.");
11817
+ console.error("Check your internet connection and try again. If the problem persists, the service may be temporarily unavailable.");
11818
+ if (verbose && causeCode)
11819
+ console.error(` Cause: ${causeMsg ?? causeCode}`);
11208
11820
  } else if (msg.includes("METADATA_SYNC_FAILED")) {
11209
11821
  console.error("Metadata sync failed. Try again.");
11210
11822
  } else if (msg.includes("LEDGER_WRITE_FAILED") || msg.includes("File structure batch POST failed")) {
@@ -11212,7 +11824,13 @@ async function syncHandler(options) {
11212
11824
  "Failed communication with the knowledge graph. The files are up to date but metadata is not. Run the tool again to sync metadata. This is a swift job."
11213
11825
  );
11214
11826
  } else {
11215
- console.error("[syncHandler] Unexpected error:", err);
11827
+ console.error("An unexpected error occurred during sync.");
11828
+ if (verbose) {
11829
+ console.error("Details:", err);
11830
+ } else {
11831
+ console.error(` ${msg}`);
11832
+ console.error("Re-run with --verbose for more details.");
11833
+ }
11216
11834
  }
11217
11835
  process.exit(1);
11218
11836
  }
@@ -11675,6 +12293,100 @@ Project created \u2705`);
11675
12293
  }
11676
12294
  }
11677
12295
 
12296
+ // apps/desktop/cue-cli/src/cue-cli-app-builder-tools.ts
12297
+ var import_commander = require("commander");
12298
+ async function buildCueClient(options) {
12299
+ const key = options.key ?? process.env["CUE_API_KEY"];
12300
+ if (!key) {
12301
+ console.error("API key is required. Provide it via --key or CUE_API_KEY env variable.");
12302
+ process.exit(1);
12303
+ }
12304
+ const cue = new CueNode({
12305
+ apiKey: FIREBASE_CONFIG().apiKey,
12306
+ appId: FIREBASE_CONFIG().appId,
12307
+ measurementId: FIREBASE_CONFIG().measurementId,
12308
+ environment: options.emulators ? "emulator" : "production",
12309
+ ...options.emulators ? { endpoints: getEmulatorEndpoints() } : {}
12310
+ });
12311
+ await cue.auth.signInWithApiKey(key);
12312
+ return cue;
12313
+ }
12314
+ async function listProjectsHandler(options) {
12315
+ try {
12316
+ const cue = await buildCueClient(options);
12317
+ if (options.verbose)
12318
+ console.error("Fetching projects...");
12319
+ const projects = await cue.api.projects.listProjects();
12320
+ const output = projects.map((p) => ({
12321
+ id: p.id,
12322
+ name: p.name,
12323
+ organizationID: p.organizationID,
12324
+ isPublic: p.isPublic,
12325
+ graphType: p.projectSettings?.graph?.type ?? "qlever",
12326
+ lastSync: p.lastSync
12327
+ }));
12328
+ console.log(JSON.stringify(output, null, 2));
12329
+ process.exit(0);
12330
+ } catch (err) {
12331
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12332
+ process.exit(1);
12333
+ }
12334
+ }
12335
+ async function entitySummaryGraphHandler(options) {
12336
+ try {
12337
+ const cue = await buildCueClient(options);
12338
+ if (options.verbose)
12339
+ console.error(`Fetching entity summary graph for project ${options.space}...`);
12340
+ const entities = new CueProjectEntities(cue.api, options.space);
12341
+ if (options.format === "graph") {
12342
+ const graph = await entities.buildSummaryGraph("graph");
12343
+ console.log(JSON.stringify(graph, null, 2));
12344
+ } else if (options.format === "md") {
12345
+ const md = await entities.buildSummaryGraph("md");
12346
+ console.log(md);
12347
+ } else {
12348
+ const raw = await entities.buildSummaryGraph();
12349
+ console.log(JSON.stringify(raw, null, 2));
12350
+ }
12351
+ process.exit(0);
12352
+ } catch (err) {
12353
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12354
+ process.exit(1);
12355
+ }
12356
+ }
12357
+ async function sparqlHandler(options) {
12358
+ try {
12359
+ let sparqlQuery = options.query;
12360
+ if (!sparqlQuery) {
12361
+ if (process.stdin.isTTY) {
12362
+ console.error("Provide a SPARQL query via --query or pipe it via stdin.");
12363
+ process.exit(1);
12364
+ }
12365
+ const chunks = [];
12366
+ for await (const chunk of process.stdin)
12367
+ chunks.push(chunk);
12368
+ sparqlQuery = Buffer.concat(chunks).toString("utf8").trim();
12369
+ }
12370
+ if (!sparqlQuery) {
12371
+ console.error("SPARQL query is empty.");
12372
+ process.exit(1);
12373
+ }
12374
+ const cue = await buildCueClient(options);
12375
+ if (options.verbose)
12376
+ console.error(`Executing SPARQL query against project ${options.space}...`);
12377
+ const result = await cue.api.sparql(sparqlQuery, options.space);
12378
+ console.log(JSON.stringify(result, null, 2));
12379
+ process.exit(0);
12380
+ } catch (err) {
12381
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12382
+ process.exit(1);
12383
+ }
12384
+ }
12385
+ var appBuilderToolsCommand = new import_commander.Command("app-builder-tools").description("Tools for agent-assisted Cue app development. Outputs JSON for machine consumption.");
12386
+ appBuilderToolsCommand.command("list-projects").description("List all projects accessible to the authenticated user").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(listProjectsHandler);
12387
+ appBuilderToolsCommand.command("entity-summary-graph").description("Fetch the entity category relationship summary graph for a project").requiredOption("-s, --space <id>", "Project ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-f, --format <format>", "Output format: md (aligned text), graph (JSON nodes+edges), json (raw SPARQL)", "md").option("-v, --verbose", "Enable verbose output", false).action(entitySummaryGraphHandler);
12388
+ appBuilderToolsCommand.command("sparql").description("Execute a SPARQL SELECT query against a project triplestore. Query via --query or stdin.").requiredOption("-s, --space <id>", "Project ID (required)").option("-q, --query <sparql>", "SPARQL query string (or pipe query via stdin)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(sparqlHandler);
12389
+
11678
12390
  // apps/desktop/cue-cli/src/main.ts
11679
12391
  var packageJson;
11680
12392
  try {
@@ -11687,7 +12399,7 @@ try {
11687
12399
  console.warn("Could not find package.json, using fallback version");
11688
12400
  }
11689
12401
  }
11690
- var program = new import_commander.Command();
12402
+ var program = new import_commander2.Command();
11691
12403
  program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
11692
12404
  program.command("sync").description("Sync files to Cue").option("-s, --space <id>", "Specify the space ID (omit to pick interactively)").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).option("--metadata-only", "Push filesystem-structure metadata for all local files without checking credits or remote state", false).action(syncHandler);
11693
12405
  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);
@@ -11697,4 +12409,5 @@ program.command("repair-ttl").description("Repair TTL files in the specified spa
11697
12409
  program.command("util-rdf-compare").description("Compare two Turtle (.ttl) files and report semantic differences").requiredOption("--file1 <path>", "Path to the first TTL file").requiredOption("--file2 <path>", "Path to the second TTL file").option("-v, --verbose", "Enable verbose output", false).action(utilRdfCompareHandler);
11698
12410
  program.command("util-remove-rdf-star").description("Remove RDF-star (quoted) triples from an NQuads file. Supports .nq and .nq.gz input/output.").requiredOption("-i, --input <path>", "Input file path (.nq or .nq.gz)").requiredOption("-o, --output <path>", "Output file path (.nq or .nq.gz)").option("-v, --verbose", "Enable verbose output", false).action(utilRemoveRdfStarHandler);
11699
12411
  program.command("create-project").description("Interactively create a new project under an organisation").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Uses emulators", false).option("-v, --verbose", "Enable verbose output", false).action(createProjectHandler);
12412
+ program.addCommand(appBuilderToolsCommand);
11700
12413
  program.parse(process.argv);