@qaecy/cue-cli 0.0.45 → 0.0.47

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 (3) hide show
  1. package/main.js +2269 -46
  2. package/package.json +1 -1
  3. package/readme.md +36 -0
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();
@@ -5090,13 +5137,17 @@ var BUCKET_PERSISTENCE2 = "db_persistence_eu_west6";
5090
5137
  var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
5091
5138
  var ENDPOINT_PROFILE_ORGANIZATIONS = "/data-views/admin/profile/organizations";
5092
5139
  var ENDPOINT_PROFILE_API_KEYS = "/data-views/admin/profile/api-keys";
5140
+ var ENDPOINT_COMMANDS_PROFILE_API_KEYS = "/commands/admin/profile/api-keys";
5141
+ var ENDPOINT_COMMANDS_PROFILE_TERMS = "/commands/admin/profile/terms";
5093
5142
  var ENDPOINT_ORG_MEMBERS = (orgId) => `/data-views/admin/organizations/${orgId}/members`;
5094
5143
  var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
5095
5144
  var ENDPOINT_SEARCH = "/assistant/search";
5096
5145
  var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5097
5146
  var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5147
+ var ENDPOINT_FUSEKI_SHACL = "/triplestore/shacl";
5098
5148
  var ENDPOINT_QLEVER_QUERY = "/qlever-server/qlever/query";
5099
5149
  var ENDPOINT_QLEVER_UPDATE = "/qlever-server/qlever/update";
5150
+ var ENDPOINT_QLEVER_SHACL = "/qlever-server/qlever/shacl";
5100
5151
  var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
5101
5152
  var MICROSOFT_PROVIDER_ID = "microsoft.com";
5102
5153
  var SUPERADMIN_ROLE = "superadmin";
@@ -5221,6 +5272,28 @@ var CueAuth = class {
5221
5272
  async signOut() {
5222
5273
  await (0, import_auth3.signOut)(this._auth);
5223
5274
  }
5275
+ /**
5276
+ * Register a new user by name and email.
5277
+ * The backend validates that the email domain belongs to an existing organisation,
5278
+ * creates the Firebase Auth account, assigns org membership, and dispatches a
5279
+ * "set your password" email to the address provided.
5280
+ * Returns the new user's UID and the organisation name on success.
5281
+ */
5282
+ async signUp(name, email) {
5283
+ const response = await fetch(
5284
+ `${this._endpoints.gatewayUrl}/commands/admin/user/signup`,
5285
+ {
5286
+ method: "POST",
5287
+ headers: { "Content-Type": "application/json" },
5288
+ body: JSON.stringify({ name, email })
5289
+ }
5290
+ );
5291
+ if (!response.ok) {
5292
+ const body = await response.json().catch(() => ({}));
5293
+ throw new Error(body?.message ?? `Sign-up failed (${response.status})`);
5294
+ }
5295
+ return response.json();
5296
+ }
5224
5297
  /** Currently signed-in user, or null if not authenticated */
5225
5298
  get currentUser() {
5226
5299
  return this._auth.currentUser;
@@ -5266,6 +5339,49 @@ var CueAuth = class {
5266
5339
  }
5267
5340
  };
5268
5341
 
5342
+ // libs/js/cue-sdk/src/lib/tables.ts
5343
+ var ENDPOINT_DATA_VIEWS_TABLES = "/data-views/tables";
5344
+ var ENDPOINT_COMMANDS_TABLES = "/commands/tables";
5345
+ var CueTables = class {
5346
+ constructor(_auth, _gatewayUrl) {
5347
+ this._auth = _auth;
5348
+ this._gatewayUrl = _gatewayUrl;
5349
+ }
5350
+ async listTables(projectId) {
5351
+ const response = await this._auth.authenticatedFetch(
5352
+ `${this._gatewayUrl}${ENDPOINT_DATA_VIEWS_TABLES}`,
5353
+ {
5354
+ headers: {
5355
+ "Content-Type": "application/json",
5356
+ "x-project-id": projectId,
5357
+ "cue-project-id": projectId
5358
+ }
5359
+ }
5360
+ );
5361
+ if (!response.ok) {
5362
+ throw new Error(`Failed to list tables: ${response.status} ${response.statusText}`);
5363
+ }
5364
+ return response.json();
5365
+ }
5366
+ async saveTables(tables, projectId) {
5367
+ const response = await this._auth.authenticatedFetch(
5368
+ `${this._gatewayUrl}${ENDPOINT_COMMANDS_TABLES}`,
5369
+ {
5370
+ method: "PUT",
5371
+ headers: {
5372
+ "Content-Type": "application/json",
5373
+ "x-project-id": projectId,
5374
+ "cue-project-id": projectId
5375
+ },
5376
+ body: JSON.stringify(tables)
5377
+ }
5378
+ );
5379
+ if (!response.ok) {
5380
+ throw new Error(`Failed to save tables: ${response.status} ${response.statusText}`);
5381
+ }
5382
+ }
5383
+ };
5384
+
5269
5385
  // libs/js/cue-sdk/src/lib/api.ts
5270
5386
  var CueApi = class {
5271
5387
  constructor(_auth, _gatewayUrl, projects, sync) {
@@ -5273,6 +5389,14 @@ var CueApi = class {
5273
5389
  this._gatewayUrl = _gatewayUrl;
5274
5390
  this.projects = projects;
5275
5391
  this.sync = sync;
5392
+ this.tables = new CueTables(_auth, _gatewayUrl);
5393
+ }
5394
+ tables;
5395
+ /** Active language used for language-sensitive SPARQL queries across all project classes. */
5396
+ language = "en";
5397
+ /** Updates the active language. All project classes (`CueProjectSchema`, `CueProjectDocuments`, `CueProjectEntities`) read this at query time. */
5398
+ setLanguage(lang) {
5399
+ this.language = lang;
5276
5400
  }
5277
5401
  /**
5278
5402
  * Returns standard authentication headers for the current user.
@@ -5316,9 +5440,12 @@ var CueApi = class {
5316
5440
  * The user must be authenticated before calling this.
5317
5441
  */
5318
5442
  async sparql(query3, projectId, graphType) {
5319
- console.log(graphType);
5320
- const endpoint = graphType === "qlever" ? ENDPOINT_QLEVER_QUERY : ENDPOINT_FUSEKI_QUERY;
5321
- console.log(`Executing SPARQL query against ${endpoint} for project ${projectId} with graph type ${graphType ?? "fuseki"}`);
5443
+ if (!graphType) {
5444
+ const project = await this.projects.getProject(projectId);
5445
+ graphType = project?.projectSettings?.graph?.type ?? "qlever";
5446
+ }
5447
+ const endpoint = graphType === "fuseki" ? ENDPOINT_FUSEKI_QUERY : ENDPOINT_QLEVER_QUERY;
5448
+ console.log(`Executing SPARQL query against ${endpoint} for project ${projectId} with graph type ${graphType}`);
5322
5449
  const urlencoded = new URLSearchParams();
5323
5450
  urlencoded.append("query", query3);
5324
5451
  const response = await this._auth.authenticatedFetch(
@@ -5341,6 +5468,57 @@ var CueApi = class {
5341
5468
  }
5342
5469
  return response.json();
5343
5470
  }
5471
+ /**
5472
+ * Validate a SHACL shape against the project's triplestore.
5473
+ *
5474
+ * @param shape - SHACL shapes graph in Turtle syntax.
5475
+ * @param projectId - Project to validate against.
5476
+ * @param options - `format`: `'json-ld'` (default, structured result) or `'turtle'` (raw string).
5477
+ * `verbose`: include server-side timing logs.
5478
+ *
5479
+ * Returns a {@link ShaclValidationReport} for JSON-LD, or a Turtle string.
5480
+ */
5481
+ async shacl(shape, projectId, options) {
5482
+ const fmt = options?.format ?? "json-ld";
5483
+ const accept = fmt === "turtle" ? "text/turtle" : "application/ld+json";
5484
+ if (options?.graphType === "fuseki") {
5485
+ const response2 = await this._auth.authenticatedFetch(
5486
+ `${this._gatewayUrl}${ENDPOINT_FUSEKI_SHACL}`,
5487
+ {
5488
+ method: "POST",
5489
+ headers: {
5490
+ "Content-Type": "text/turtle",
5491
+ "Accept": accept,
5492
+ "x-project-id": projectId,
5493
+ "cue-project-id": projectId
5494
+ },
5495
+ body: shape
5496
+ }
5497
+ );
5498
+ if (!response2.ok) {
5499
+ const msg = await response2.text().catch(() => "");
5500
+ throw new Error(`SHACL validation failed: ${response2.status} ${response2.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5501
+ }
5502
+ return fmt === "turtle" ? response2.text() : response2.json();
5503
+ }
5504
+ const url = `${this._gatewayUrl}${ENDPOINT_QLEVER_SHACL}${options?.verbose ? "?verbose=true" : ""}`;
5505
+ const body = new URLSearchParams({ shape });
5506
+ const response = await this._auth.authenticatedFetch(url, {
5507
+ method: "POST",
5508
+ headers: {
5509
+ "Content-Type": "application/x-www-form-urlencoded",
5510
+ "Accept": accept,
5511
+ "x-project-id": projectId,
5512
+ "cue-project-id": projectId
5513
+ },
5514
+ body
5515
+ });
5516
+ if (!response.ok) {
5517
+ const msg = await response.text().catch(() => "");
5518
+ throw new Error(`SHACL validation failed: ${response.status} ${response.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5519
+ }
5520
+ return fmt === "turtle" ? response.text() : response.json();
5521
+ }
5344
5522
  async getConsumption(projectId) {
5345
5523
  const response = await this._auth.authenticatedFetch(
5346
5524
  `${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`,
@@ -5361,6 +5539,1730 @@ var CueApi = class {
5361
5539
  }
5362
5540
  };
5363
5541
 
5542
+ // libs/js/cue-gis/src/lib/models.ts
5543
+ var FEATURE_CATEGORIES = [
5544
+ "building",
5545
+ "cadastre",
5546
+ "zone",
5547
+ "address",
5548
+ "poi",
5549
+ "greenspace",
5550
+ "paved",
5551
+ "railway",
5552
+ "natural",
5553
+ "manmade"
5554
+ ];
5555
+ var NOMINATIM_FEATURE_CATEGORIES = [
5556
+ "address",
5557
+ "poi",
5558
+ "railway",
5559
+ "natural",
5560
+ "manmade"
5561
+ ];
5562
+ var FEATURE_CATEGORY_DESCRIPTORS = {
5563
+ address: {
5564
+ category: "address",
5565
+ label: "Address",
5566
+ description: "Official addresses, streets, and addressable access points.",
5567
+ preferredColor: "#1d4ed8"
5568
+ },
5569
+ poi: {
5570
+ category: "poi",
5571
+ label: "Points of interest",
5572
+ description: "Places and amenities relevant to everyday operations and access.",
5573
+ preferredColor: "#059669"
5574
+ },
5575
+ railway: {
5576
+ category: "railway",
5577
+ label: "Railway",
5578
+ description: "Rail infrastructure, stations, and rail-adjacent transport context.",
5579
+ preferredColor: "#dc2626"
5580
+ },
5581
+ natural: {
5582
+ category: "natural",
5583
+ label: "Natural features",
5584
+ description: "Natural and hydrological features such as watercourses, forests, and land cover.",
5585
+ preferredColor: "#0ea5e9"
5586
+ },
5587
+ manmade: {
5588
+ category: "manmade",
5589
+ label: "Built features",
5590
+ description: "Structures and man-made elements not covered by more specific categories.",
5591
+ preferredColor: "#ca8a04"
5592
+ },
5593
+ cadastre: {
5594
+ category: "cadastre",
5595
+ label: "Cadastre",
5596
+ description: "Land parcels, legal boundaries, official surveying, and public-law land restrictions.",
5597
+ preferredColor: "#7c3aed"
5598
+ },
5599
+ building: {
5600
+ category: "building",
5601
+ label: "Buildings",
5602
+ description: "Building footprints and structures from official registry or cadastral data.",
5603
+ preferredColor: "#f97316"
5604
+ },
5605
+ greenspace: {
5606
+ category: "greenspace",
5607
+ label: "Green spaces",
5608
+ description: "Parks, gardens, playgrounds, sports facilities, and other vegetated open areas.",
5609
+ preferredColor: "#16a34a"
5610
+ },
5611
+ paved: {
5612
+ category: "paved",
5613
+ label: "Paved surfaces",
5614
+ description: "Courtyards, sidewalks, parking areas, and other sealed ground surfaces.",
5615
+ preferredColor: "#78716c"
5616
+ },
5617
+ zone: {
5618
+ category: "zone",
5619
+ label: "Planning zones",
5620
+ description: "Land-use zones, local plans, and public-law planning constraints (Nutzungsplanung / Lokalplan).",
5621
+ preferredColor: "#db2777"
5622
+ }
5623
+ };
5624
+ function featureCategoryDescriptor(category) {
5625
+ return FEATURE_CATEGORY_DESCRIPTORS[category];
5626
+ }
5627
+ var ZONE_PLAN_TYPE_COLORS = {
5628
+ "land-use-plan": "#f59e0b",
5629
+ // amber – primary zoning
5630
+ "local-plan": "#ec4899",
5631
+ // pink – Danish lokalplan
5632
+ "design-plan": "#3b82f6",
5633
+ // blue – Gestaltungsplan
5634
+ "development-plan": "#0891b2",
5635
+ // cyan – Bebauungsplan
5636
+ "special-use-plan": "#f97316",
5637
+ // orange – Sondernutzungsplan
5638
+ "open-space-zone": "#22c55e",
5639
+ // green – Freihaltezone
5640
+ "overlay-regulation": "#a855f7",
5641
+ // purple – overlaying regulations
5642
+ "unzoned": "#94a3b8",
5643
+ // slate-400 – nicht zonierte Fläche
5644
+ "municipal-plan-framework": "#6366f1",
5645
+ // indigo – Kommuneplanramme
5646
+ "neighbourhood-conservation-plan": "#14b8a6",
5647
+ // teal – Quartiererhaltungszonenplan
5648
+ "noise-sensitivity-plan": "#eab308",
5649
+ // yellow – Lärmempfindlichkeitsstufenplan
5650
+ "water-protection-plan": "#06b6d4",
5651
+ // sky – Gewässerschutzzonenplan
5652
+ "nature-landscape-plan": "#16a34a"
5653
+ // dark-green – Natur- und Landschaftsschutz
5654
+ };
5655
+ function bboxIntersects(a5, b) {
5656
+ return a5[0] < b[2] && a5[2] > b[0] && a5[1] < b[3] && a5[3] > b[1];
5657
+ }
5658
+
5659
+ // libs/js/cue-gis/src/lib/nominatim-adapter.ts
5660
+ var NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org";
5661
+ var DEFAULT_USER_AGENT = "cue-gis/0.0.1";
5662
+ var LAYER_PROBE_QUERY = {
5663
+ address: "street",
5664
+ poi: "shop",
5665
+ railway: "station",
5666
+ natural: "park",
5667
+ manmade: "building",
5668
+ cadastre: "parcel",
5669
+ building: "building",
5670
+ greenspace: "park",
5671
+ paved: "road",
5672
+ zone: "boundary"
5673
+ };
5674
+ var NOMINATIM_SOURCE_ID = "nominatim";
5675
+ function toLayerId(category) {
5676
+ return `${NOMINATIM_SOURCE_ID}:${category}`;
5677
+ }
5678
+ function toLayerDescriptor(category) {
5679
+ const descriptor = featureCategoryDescriptor(category);
5680
+ return {
5681
+ id: toLayerId(category),
5682
+ sourceId: NOMINATIM_SOURCE_ID,
5683
+ sourceLayerId: category,
5684
+ category,
5685
+ preferredColor: descriptor.preferredColor,
5686
+ tier: "raw",
5687
+ label: descriptor.label,
5688
+ description: `Nominatim ${category} layer`,
5689
+ labelKey: `gis.layer.${NOMINATIM_SOURCE_ID}.${category}.label`,
5690
+ descriptionKey: `gis.layer.${NOMINATIM_SOURCE_ID}.${category}.description`
5691
+ };
5692
+ }
5693
+ var NominatimAdapter = class {
5694
+ baseUrl;
5695
+ userAgent;
5696
+ email;
5697
+ limit;
5698
+ constructor(options = {}) {
5699
+ this.baseUrl = options.baseUrl ?? NOMINATIM_BASE_URL;
5700
+ this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
5701
+ this.email = options.email;
5702
+ this.limit = Math.min(options.limit ?? 40, 40);
5703
+ }
5704
+ async listFeatureCategoryDescriptors(bbox) {
5705
+ const checks = await Promise.allSettled(
5706
+ NOMINATIM_FEATURE_CATEGORIES.map(async (category) => {
5707
+ const results = await this.search(bbox, category);
5708
+ return { category, hasResults: results.length > 0 };
5709
+ })
5710
+ );
5711
+ return checks.filter(
5712
+ (r) => r.status === "fulfilled" && r.value.hasResults
5713
+ ).map((r) => featureCategoryDescriptor(r.value.category));
5714
+ }
5715
+ async listAvailableLayers(bbox) {
5716
+ const categories = await this.listFeatureCategoryDescriptors(bbox);
5717
+ return categories.map((category) => toLayerDescriptor(category.category));
5718
+ }
5719
+ async getFeaturesForLayer(bbox, layerId) {
5720
+ const category = FEATURE_CATEGORIES.find((candidate) => toLayerId(candidate) === layerId);
5721
+ if (!category) {
5722
+ return [];
5723
+ }
5724
+ return this.search(bbox, category);
5725
+ }
5726
+ async listFeatureCategories(bbox) {
5727
+ const descriptors = await this.listFeatureCategoryDescriptors(bbox);
5728
+ return descriptors.map((descriptor) => descriptor.category);
5729
+ }
5730
+ async getFeaturesOfCategory(bbox, category) {
5731
+ return this.getFeaturesForLayer(bbox, toLayerId(category));
5732
+ }
5733
+ /**
5734
+ * Low-level search against the Nominatim API. Uses a per-layer probe query
5735
+ * so bounded viewbox searches return meaningful results.
5736
+ */
5737
+ async search(bbox, layer) {
5738
+ const [west, south, east, north] = bbox;
5739
+ const q = layer ? LAYER_PROBE_QUERY[layer] : "place";
5740
+ const params = new URLSearchParams({
5741
+ q,
5742
+ format: "jsonv2",
5743
+ viewbox: `${west},${north},${east},${south}`,
5744
+ bounded: "1",
5745
+ limit: String(this.limit),
5746
+ addressdetails: "1",
5747
+ extratags: "1",
5748
+ dedupe: "1"
5749
+ });
5750
+ if (layer) {
5751
+ params.set("layer", layer);
5752
+ }
5753
+ if (this.email) {
5754
+ params.set("email", this.email);
5755
+ }
5756
+ const url = `${this.baseUrl}/search?${params.toString()}`;
5757
+ const response = await fetch(url, {
5758
+ headers: {
5759
+ "User-Agent": this.userAgent,
5760
+ Accept: "application/json"
5761
+ }
5762
+ });
5763
+ if (!response.ok) {
5764
+ throw new Error(
5765
+ `Nominatim request failed: ${response.status} ${response.statusText}`
5766
+ );
5767
+ }
5768
+ const results = await response.json();
5769
+ return results.map((r) => this.toGisFeature(r, layer));
5770
+ }
5771
+ toGisFeature(r, category) {
5772
+ const [s, n, w, e] = r.boundingbox;
5773
+ const resolvedCategory = category ?? r.category;
5774
+ const descriptor = featureCategoryDescriptor(resolvedCategory);
5775
+ return {
5776
+ id: `${r.osm_type}/${r.osm_id}`,
5777
+ sourceId: NOMINATIM_SOURCE_ID,
5778
+ sourceFeatureId: `${r.osm_type}/${r.osm_id}`,
5779
+ layerId: toLayerId(resolvedCategory),
5780
+ name: r.name ?? r.display_name.split(",")[0].trim(),
5781
+ category: resolvedCategory,
5782
+ preferredColor: descriptor.preferredColor,
5783
+ type: r.type,
5784
+ lat: parseFloat(r.lat),
5785
+ lon: parseFloat(r.lon),
5786
+ displayName: r.display_name,
5787
+ bbox: [parseFloat(w), parseFloat(s), parseFloat(e), parseFloat(n)],
5788
+ tier: "raw",
5789
+ originalData: r
5790
+ };
5791
+ }
5792
+ };
5793
+
5794
+ // libs/js/cue-gis/src/lib/use-classifier.ts
5795
+ var DK_BBR_BUILDING = {
5796
+ // 100s – Residential
5797
+ "110": "residential",
5798
+ "120": "residential",
5799
+ "121": "residential",
5800
+ "122": "residential",
5801
+ "130": "residential",
5802
+ "131": "residential",
5803
+ "132": "residential",
5804
+ "140": "residential",
5805
+ "150": "residential",
5806
+ "160": "residential",
5807
+ "190": "residential",
5808
+ // 200s – Recreational / holiday
5809
+ "210": "recreational",
5810
+ "212": "recreational",
5811
+ "220": "recreational",
5812
+ "221": "recreational",
5813
+ "230": "recreational",
5814
+ "290": "residential",
5815
+ // 300s – Agricultural
5816
+ "310": "agricultural",
5817
+ "311": "agricultural",
5818
+ "312": "agricultural",
5819
+ "320": "agricultural",
5820
+ "321": "agricultural",
5821
+ "322": "agricultural",
5822
+ "330": "agricultural",
5823
+ "390": "agricultural",
5824
+ // 400s – Commercial
5825
+ "410": "commercial",
5826
+ "411": "commercial",
5827
+ "420": "commercial",
5828
+ "421": "commercial",
5829
+ "422": "commercial",
5830
+ "430": "commercial",
5831
+ "431": "commercial",
5832
+ "432": "commercial",
5833
+ "440": "commercial",
5834
+ "490": "commercial",
5835
+ // 500s – Industrial / infrastructure
5836
+ "510": "industrial",
5837
+ "511": "industrial",
5838
+ "519": "industrial",
5839
+ "520": "infrastructure",
5840
+ "521": "infrastructure",
5841
+ "522": "infrastructure",
5842
+ "529": "infrastructure",
5843
+ "530": "infrastructure",
5844
+ "531": "infrastructure",
5845
+ "532": "infrastructure",
5846
+ "533": "infrastructure",
5847
+ "534": "infrastructure",
5848
+ "535": "infrastructure",
5849
+ "540": "infrastructure",
5850
+ "585": "infrastructure",
5851
+ "590": "industrial",
5852
+ // 600s – Public / institutional
5853
+ "610": "public",
5854
+ "620": "public",
5855
+ "621": "public",
5856
+ "622": "public",
5857
+ "630": "public",
5858
+ "640": "public",
5859
+ "650": "recreational",
5860
+ "660": "public",
5861
+ "670": "public",
5862
+ "680": "public",
5863
+ "690": "public",
5864
+ // 900s – Accessory / utility
5865
+ "910": "other",
5866
+ "920": "other",
5867
+ "930": "other",
5868
+ "940": "other",
5869
+ "950": "infrastructure",
5870
+ // Teknikbygning (utility / technical building)
5871
+ "960": "agricultural",
5872
+ "970": "agricultural",
5873
+ "990": "other"
5874
+ };
5875
+ var CH_GWR_BUILDING = {
5876
+ "1010": "residential",
5877
+ "1020": "mixed",
5878
+ "1030": "residential",
5879
+ "1040": "other",
5880
+ "1060": "recreational",
5881
+ "1080": "residential",
5882
+ "1110": "commercial",
5883
+ "1120": "commercial",
5884
+ "1130": "commercial",
5885
+ "1140": "industrial",
5886
+ "1150": "infrastructure",
5887
+ "1160": "infrastructure",
5888
+ "1210": "public",
5889
+ "1220": "public",
5890
+ "1230": "public",
5891
+ "1240": "recreational",
5892
+ "1241": "public",
5893
+ "1242": "recreational",
5894
+ "1251": "public",
5895
+ "1261": "agricultural",
5896
+ "1262": "agricultural",
5897
+ "1263": "agricultural",
5898
+ "1275": "other",
5899
+ "1276": "other",
5900
+ "1277": "infrastructure",
5901
+ "1278": "infrastructure",
5902
+ "1281": "infrastructure",
5903
+ "1282": "infrastructure",
5904
+ "9999": "other"
5905
+ };
5906
+ var CH_GWR_GKLAS = {
5907
+ // Residential
5908
+ "1110": "residential",
5909
+ // Einfamilienhaus
5910
+ "1121": "recreational",
5911
+ // Ferienhaus / Sommerwohnsitz
5912
+ "1122": "residential",
5913
+ // Mehrfamilienhaus
5914
+ "1130": "mixed",
5915
+ // Wohn- und Geschäftshaus
5916
+ "1140": "mixed",
5917
+ // Wohn- und Landwirtschaftsgebäude
5918
+ // Hospitality
5919
+ "1211": "commercial",
5920
+ // Hotel, Motel
5921
+ "1212": "commercial",
5922
+ // Gaststätten, Restaurant
5923
+ "1213": "mixed",
5924
+ // Pension, Heim (care home / pension)
5925
+ // Office / commercial
5926
+ "1220": "commercial",
5927
+ // Büro- und Verwaltungsgebäude
5928
+ "1230": "commercial",
5929
+ // Handelsbau (retail)
5930
+ "1231": "commercial",
5931
+ // Grosshandel / Grossmarkt
5932
+ // Infrastructure / transport
5933
+ "1241": "infrastructure",
5934
+ // Bahnhof, Flughafen
5935
+ // Leisure
5936
+ "1242": "recreational",
5937
+ // Freizeitgebäude
5938
+ "1264": "recreational",
5939
+ // Sportgebäude
5940
+ // Industrial
5941
+ "1251": "industrial",
5942
+ // Fabrik, Werkstatt
5943
+ "1252": "industrial",
5944
+ // Lagerhalle, Silo
5945
+ // Public / institutional
5946
+ "1261": "public",
5947
+ // Schulen, Bildungsstätten
5948
+ "1262": "public",
5949
+ // Spitäler, Kliniken
5950
+ "1263": "public",
5951
+ // Universität, Forschungsstätte
5952
+ "1265": "public",
5953
+ // Polizei, Militär, Gefängnis
5954
+ "1272": "public",
5955
+ // Religiöse Gebäude und Kultbauten
5956
+ "1273": "public",
5957
+ // Öffentliche Verwaltungsgebäude
5958
+ // Agricultural
5959
+ "1271": "agricultural",
5960
+ // Landwirtschaftsgebäude
5961
+ // Other
5962
+ "1274": "other",
5963
+ // Anderes Gebäude
5964
+ "1281": "infrastructure",
5965
+ // Empfangsgebäude Bahn/Bus
5966
+ "1282": "infrastructure"
5967
+ // Parkhaus, Garage
5968
+ };
5969
+ var KEYWORD_RULES = [
5970
+ // "Gebäude" is the Swiss AV BoFlaeche land-cover label for a building footprint.
5971
+ // It does not encode the actual use type, so map it to 'other' as a safe fallback.
5972
+ [/^geb[äa]ude$/i, "other"],
5973
+ [/wohn|resident|housing|bolig|enfamil|etagebolig|rækkehus|lejlighed/i, "residential"],
5974
+ [/büro|office|handel|commercial|butik|forretning|kontor|supermarked|lager|hotel|restaurant/i, "commercial"],
5975
+ [/industri|fabrik|industrial|gewerbe|produktion|fabrik|fremstill/i, "industrial"],
5976
+ [/landwirtschaft|agrar|agricultural|landbrug|skov|dyrk|stald|lade|drivhus/i, "agricultural"],
5977
+ [/schule|school|hospital|gesundheit|sundhed|kirche|church|offentlig|uddannelse|kultur|social|plejehjem/i, "public"],
5978
+ [/freizeit|recreation|sport|sommerhus|ferienhaus|fritid|kolonihave/i, "recreational"],
5979
+ [/infrastruktur|verkehr|transport|energy|energie|forsyning|teknik|teknisk/i, "infrastructure"],
5980
+ [/gemischt|mixed|blandet/i, "mixed"]
5981
+ ];
5982
+ function classifyByText(text) {
5983
+ for (const [pattern, use] of KEYWORD_RULES) {
5984
+ if (pattern.test(text))
5985
+ return use;
5986
+ }
5987
+ return void 0;
5988
+ }
5989
+ function classifyBuildingUse(code, sourceId) {
5990
+ if (code == null)
5991
+ return void 0;
5992
+ const key = String(code).trim();
5993
+ if (sourceId === "swiss-gwr") {
5994
+ return CH_GWR_GKLAS[key] ?? classifyByText(key);
5995
+ }
5996
+ if (sourceId === "danish-matrikel") {
5997
+ return DK_BBR_BUILDING[key] ?? classifyByText(key);
5998
+ }
5999
+ if (sourceId === "zurich-wfs" || sourceId === "swiss-av-wfs") {
6000
+ return CH_GWR_BUILDING[key] ?? classifyByText(key);
6001
+ }
6002
+ return CH_GWR_BUILDING[key] ?? DK_BBR_BUILDING[key] ?? classifyByText(key);
6003
+ }
6004
+ function classifyPlotUse(use, _sourceId) {
6005
+ if (use == null)
6006
+ return void 0;
6007
+ return classifyByText(String(use).trim());
6008
+ }
6009
+ function normKey(raw) {
6010
+ return raw.trim().toLowerCase().replace(/[\s_\-]/g, "");
6011
+ }
6012
+ var ZONE_LEGAL_STATUS_MAP = {
6013
+ // Swiss ÖREB rechtsstatus
6014
+ "inkraft": "in-force",
6015
+ "laufendeanderung": "amendment-pending",
6016
+ "laufende\xE4nderung": "amendment-pending",
6017
+ "aenderungohnevorvirkung": "amendment-pending",
6018
+ // guard against common typo
6019
+ "aenderungohnevorwirkung": "amendment-pending",
6020
+ "\xE4nderungohnevorwirkung": "amendment-pending",
6021
+ "aenderungmitvorwirkung": "amendment-in-effect",
6022
+ "\xE4nderungmitvorwirkung": "amendment-in-effect",
6023
+ "\xF6ffentlicheauflage": "proposed",
6024
+ "offentlicheauflage": "proposed",
6025
+ "aufgehoben": "repealed",
6026
+ // Danish plandata.dk status
6027
+ "vedtaget": "in-force",
6028
+ "forslag": "proposed",
6029
+ "aflyst": "repealed"
6030
+ };
6031
+ function classifyZoneLegalStatus(raw) {
6032
+ if (!raw)
6033
+ return void 0;
6034
+ return ZONE_LEGAL_STATUS_MAP[normKey(raw)];
6035
+ }
6036
+ var ZONE_PLAN_TYPE_EXACT = {
6037
+ // Swiss Nutzungsplanung (typ_gde_bezeichnung and common variants)
6038
+ "grundnutzungszonenplan": "land-use-plan",
6039
+ "nutzungszonenplan": "land-use-plan",
6040
+ "zonenplan": "land-use-plan",
6041
+ "gestaltungsplan": "design-plan",
6042
+ "bebauungsplan": "development-plan",
6043
+ "sondernutzungsplan": "special-use-plan",
6044
+ "quartiererhaltungszonenplan": "neighbourhood-conservation-plan",
6045
+ "l\xE4rmempfindlichkeitsstufenplan": "noise-sensitivity-plan",
6046
+ "laermempfindlichkeitsstufenplan": "noise-sensitivity-plan",
6047
+ "gew\xE4sserschutzzonenplan": "water-protection-plan",
6048
+ "gewasserschutzzonenplan": "water-protection-plan",
6049
+ "grundwasserschutzzonenplan": "water-protection-plan",
6050
+ "gew\xE4sserschutzplan": "water-protection-plan",
6051
+ "naturundlandschaftsschutzzonenplan": "nature-landscape-plan",
6052
+ "natur-undlandschaftsschutzzonenplan": "nature-landscape-plan",
6053
+ "landschaftsschutzzonenplan": "nature-landscape-plan",
6054
+ // Freihaltezone — open space / clearance zone within the Grundnutzungszonenplan
6055
+ "freihaltezone": "open-space-zone",
6056
+ // Simple Grundnutzung zone type designations (municipality labels from ogd-0156)
6057
+ "kernzone": "land-use-plan",
6058
+ "wald": "land-use-plan",
6059
+ "gew\xE4sser": "land-use-plan",
6060
+ "gewasser": "land-use-plan",
6061
+ "reservezone": "land-use-plan",
6062
+ "bahnareal": "land-use-plan",
6063
+ "erholungszone": "land-use-plan",
6064
+ // Road / transport area designations
6065
+ "strassen(weitere)": "land-use-plan",
6066
+ "strassen": "land-use-plan",
6067
+ // Unzoned areas
6068
+ "nichtzoniertfl\xE4che": "unzoned",
6069
+ "nichtzoniertefl\xE4che": "unzoned",
6070
+ "nichtzoniertflache": "unzoned",
6071
+ "nichtzonierteflache": "unzoned",
6072
+ "nichtzoniertgem\xE4ssbzo2016": "unzoned",
6073
+ "nichtzoniertgemassbzo2016": "unzoned",
6074
+ // Overlay building/development regulations (Überlagernde Festlegungen)
6075
+ "hochh\xE4user": "overlay-regulation",
6076
+ "hochhauser": "overlay-regulation",
6077
+ "areal\xFCberbauungenzul\xE4ssig": "overlay-regulation",
6078
+ "arealuberbauungenzulassig": "overlay-regulation",
6079
+ "erh\xF6hteausnutzung": "overlay-regulation",
6080
+ "erhohteausnutzung": "overlay-regulation",
6081
+ "erdgeschossnutzung": "overlay-regulation",
6082
+ "freifl\xE4chenzifferverlegungsgebiet": "overlay-regulation",
6083
+ "freiflachenzifferverlegungsgebiet": "overlay-regulation",
6084
+ "baumschutz": "overlay-regulation",
6085
+ // Special building regulations (Sonderbauvorschriften)
6086
+ "sonderbauvorschriften": "overlay-regulation",
6087
+ // Danish plandata.dk
6088
+ "lokalplan": "local-plan",
6089
+ "kommuneplanramme": "municipal-plan-framework"
6090
+ };
6091
+ var ZONE_PLAN_TYPE_KEYWORDS = [
6092
+ [/quartiererhalt/i, "neighbourhood-conservation-plan"],
6093
+ [/lärmempfindlich|laermempfindlich/i, "noise-sensitivity-plan"],
6094
+ [/gewässerschutz|grundwasserschutz/i, "water-protection-plan"],
6095
+ [/natur.*landschaft|landschaft.*schutz/i, "nature-landscape-plan"],
6096
+ [/gestaltungsplan/i, "design-plan"],
6097
+ [/bebauungsplan/i, "development-plan"],
6098
+ [/sondernutzung/i, "special-use-plan"],
6099
+ // Freihaltezone / open-space clearance zones (before general zonenplan catch-all)
6100
+ [/freihalt/i, "open-space-zone"],
6101
+ // Zone für öffentliche Bauten — public-facilities land-use zone
6102
+ [/öffentliche.*bauten|zone.*öffentlich/i, "land-use-plan"],
6103
+ // Overlaying building/development regulations
6104
+ [/hochhaus/i, "overlay-regulation"],
6105
+ [/arealüberbauung|arealuberbauung/i, "overlay-regulation"],
6106
+ [/erhöhte.*ausnutzung|ausnutzung/i, "overlay-regulation"],
6107
+ [/erdgeschossnutzung/i, "overlay-regulation"],
6108
+ [/freiflächenzifferverlegung|freiflachenzifferverlegung/i, "overlay-regulation"],
6109
+ [/erhaltenswert/i, "overlay-regulation"],
6110
+ [/m[\u00e4a]ssig.*st[\u00f6o]rend|st[\u00f6o]rendes.*gewerbe/i, "overlay-regulation"],
6111
+ // Grundnutzung zone type designations (municipality labels, ogd-0156)
6112
+ [/wohnzone/i, "land-use-plan"],
6113
+ [/kernzone|zentrumszone/i, "land-use-plan"],
6114
+ [/erholungszone|erholungs/i, "land-use-plan"],
6115
+ [/stra[sß]en/i, "land-use-plan"],
6116
+ [/landwirtschaft/i, "land-use-plan"],
6117
+ [/\bwald\b/i, "land-use-plan"],
6118
+ [/\bgew[\u00e4a]sser\b/i, "land-use-plan"],
6119
+ // Unzoned areas
6120
+ [/nicht\s*zoniert/i, "unzoned"],
6121
+ // Tree / vegetation protection overlay
6122
+ [/baumschutz/i, "overlay-regulation"],
6123
+ // Special building regulations not under standard building ordinance
6124
+ [/sonderbauvorschrift/i, "overlay-regulation"],
6125
+ [/kommuneplan/i, "municipal-plan-framework"],
6126
+ [/lokalplan/i, "local-plan"],
6127
+ [/zonenplan|nutzungsplan|nutzungszone/i, "land-use-plan"],
6128
+ // Catch-all: any remaining Xzone / Xareal designation from the Grundnutzungszonenplan
6129
+ [/zone$|areal$/i, "land-use-plan"]
6130
+ ];
6131
+ function classifyZonePlanType(raw) {
6132
+ if (!raw)
6133
+ return void 0;
6134
+ const key = normKey(raw);
6135
+ if (key in ZONE_PLAN_TYPE_EXACT)
6136
+ return ZONE_PLAN_TYPE_EXACT[key];
6137
+ for (const [pattern, planType] of ZONE_PLAN_TYPE_KEYWORDS) {
6138
+ if (pattern.test(raw))
6139
+ return planType;
6140
+ }
6141
+ console.warn(
6142
+ `[cue-gis] Unknown zone plan type \u2014 add "${raw}" to classifyZonePlanType: no ZonePlanType mapping found.`
6143
+ );
6144
+ return void 0;
6145
+ }
6146
+
6147
+ // libs/js/cue-gis/src/lib/zurich-maps-adapter.ts
6148
+ var WFS_BASE_URL = "https://maps.zh.ch/wfs/OGDZHWFS";
6149
+ var ZURICH_SOURCE_ID = "zurich-wfs";
6150
+ var ZURICH_CANTON_BBOX = [8.35, 47.15, 8.95, 47.7];
6151
+ var SWITZERLAND_BBOX = [5.9, 45.7, 10.55, 47.85];
6152
+ function entryTypeName(entry) {
6153
+ return typeof entry === "string" ? entry : entry.typeName;
6154
+ }
6155
+ function entryCqlFilter(entry) {
6156
+ return typeof entry === "string" ? void 0 : entry.cqlFilter;
6157
+ }
6158
+ function firstCoordinate(geometry) {
6159
+ if (!geometry)
6160
+ return [0, 0];
6161
+ const coords = geometry.coordinates;
6162
+ switch (geometry.type) {
6163
+ case "Point":
6164
+ return coords;
6165
+ case "MultiPoint":
6166
+ case "LineString":
6167
+ return coords[0];
6168
+ case "MultiLineString":
6169
+ case "Polygon":
6170
+ return coords[0][0];
6171
+ case "MultiPolygon":
6172
+ return coords[0][0][0];
6173
+ default:
6174
+ return [0, 0];
6175
+ }
6176
+ }
6177
+ function pickName(props, fallback) {
6178
+ if (!props)
6179
+ return fallback;
6180
+ for (const key of ["plannavn", "bezeichnung", "name", "strassenname", "objektname", "title", "label"]) {
6181
+ if (typeof props[key] === "string" && props[key])
6182
+ return props[key];
6183
+ }
6184
+ return fallback;
6185
+ }
6186
+ function toLayerId2(sourceId, typeName, cqlFilter) {
6187
+ if (!cqlFilter)
6188
+ return `${sourceId}:${typeName}`;
6189
+ return `${sourceId}:${typeName}[${cqlFilter}]`;
6190
+ }
6191
+ var PRIORITY_CATEGORIES = /* @__PURE__ */ new Set(["building", "cadastre", "greenspace", "paved", "zone"]);
6192
+ function toLayerDescriptor2(sourceId, category, typeName, cqlFilter) {
6193
+ const shortName = typeName.replace(/^ms:/, "");
6194
+ const descriptor = featureCategoryDescriptor(category);
6195
+ const tier = PRIORITY_CATEGORIES.has(category) ? "priority" : "raw";
6196
+ return {
6197
+ id: toLayerId2(sourceId, typeName, cqlFilter),
6198
+ sourceId,
6199
+ sourceLayerId: typeName,
6200
+ category,
6201
+ preferredColor: descriptor.preferredColor,
6202
+ tier,
6203
+ label: shortName,
6204
+ description: `Z\xFCrich WFS layer ${shortName}`,
6205
+ labelKey: `gis.layer.${sourceId}.${shortName}.label`,
6206
+ descriptionKey: `gis.layer.${sourceId}.${shortName}.description`
6207
+ };
6208
+ }
6209
+ function toNormalisedBuildingProperties(props, sourceId) {
6210
+ if (!props)
6211
+ return { featureType: "building" };
6212
+ const rawArea = props["grundflaeche"] ?? props["gbf"] ?? props["gebaeudegrundrissflaeche"] ?? props["flaeche"] ?? void 0;
6213
+ const rawFloors = props["vollgeschosse"] ?? props["geschossanzahl"] ?? props["anzahl_geschosse"] ?? void 0;
6214
+ const buildingUse = props["gebaeudefunktion"] ?? props["gfkode"] ?? props["art"] ?? props["objektart"] ?? void 0;
6215
+ const rawYear = props["baujahr"] ?? props["bauperiode"] ?? void 0;
6216
+ const registryId = String(props["egid"] ?? props["gwr_egid"] ?? props["egris_egid"] ?? "").trim() || void 0;
6217
+ return {
6218
+ featureType: "building",
6219
+ areaM2: typeof rawArea === "number" ? rawArea : void 0,
6220
+ buildingUse: buildingUse ? String(buildingUse) : void 0,
6221
+ buildingUseGeneric: classifyBuildingUse(buildingUse, sourceId),
6222
+ floors: typeof rawFloors === "number" ? rawFloors : void 0,
6223
+ yearBuilt: typeof rawYear === "number" ? rawYear : void 0,
6224
+ registryId
6225
+ };
6226
+ }
6227
+ function toNormalisedPlotProperties(props, sourceId) {
6228
+ if (!props)
6229
+ return { featureType: "plot" };
6230
+ const registryId = props["egris_egrid"] ?? props["egrid"] ?? void 0;
6231
+ const nummer = props["nummer"] ?? void 0;
6232
+ const nbident = props["nbident"] ?? void 0;
6233
+ const plotId = nummer && nbident ? `${nummer}, ${nbident}` : nummer ?? void 0;
6234
+ const rawArea = props["flaechenmass"] ?? props["flaeche"] ?? void 0;
6235
+ const plotUse = props["art"] ?? props["nutzungsart"] ?? void 0;
6236
+ return {
6237
+ featureType: "plot",
6238
+ areaM2: typeof rawArea === "number" ? rawArea : void 0,
6239
+ plotUse: plotUse ? String(plotUse) : void 0,
6240
+ plotUseGeneric: classifyPlotUse(plotUse, sourceId),
6241
+ plotId,
6242
+ registryId
6243
+ };
6244
+ }
6245
+ function toNormalisedGreenspaceProperties(props) {
6246
+ if (!props)
6247
+ return { featureType: "greenspace" };
6248
+ const rawArea = props["flaeche"] ?? void 0;
6249
+ const surfaceType = props["art"] ?? void 0;
6250
+ return {
6251
+ featureType: "greenspace",
6252
+ areaM2: typeof rawArea === "number" ? rawArea : void 0,
6253
+ surfaceType: surfaceType ? String(surfaceType) : void 0
6254
+ };
6255
+ }
6256
+ function toNormalisedPavedProperties(props) {
6257
+ if (!props)
6258
+ return { featureType: "paved" };
6259
+ const rawArea = props["flaeche"] ?? void 0;
6260
+ const surfaceType = props["art"] ?? void 0;
6261
+ return {
6262
+ featureType: "paved",
6263
+ areaM2: typeof rawArea === "number" ? rawArea : void 0,
6264
+ surfaceType: surfaceType ? String(surfaceType) : void 0
6265
+ };
6266
+ }
6267
+ function toNormalisedZoneProperties(props) {
6268
+ if (!props)
6269
+ return { featureType: "zone" };
6270
+ const zoneType = props["plannavn"] ?? props["bezeichnung"] ?? props["typ_bezeichnung"] ?? props["art"] ?? void 0;
6271
+ const planId = props["planid"] ?? props["plannummer"] ?? void 0;
6272
+ const zoneCode = props["artcode"] ?? props["typ_code"] ?? props["abkuerzung"] ?? planId ?? void 0;
6273
+ const rawLegalStatus = props["rechtsstatus"] ?? props["status"] ?? void 0;
6274
+ const legalStatus = classifyZoneLegalStatus(rawLegalStatus);
6275
+ const rawPlanType = props["typ_gde_bezeichnung"] ?? void 0;
6276
+ const planType = classifyZonePlanType(rawPlanType);
6277
+ const planDocumentLink = props["dagsordenpunkt_url"] ?? props["dokument_url"] ?? props["link"] ?? void 0;
6278
+ const publicationDate = props["auflagedatum"] ?? void 0;
6279
+ const fixingDate = props["festsetzungsdatum"] ?? void 0;
6280
+ const approvalDate = props["genehmigungsdatum"] ?? void 0;
6281
+ const effectiveFrom = props["inkraftsetzungsdatum"] ?? void 0;
6282
+ const rawArea = props["flaeche"] ?? props["flaeche_m2"] ?? void 0;
6283
+ return {
6284
+ featureType: "zone",
6285
+ zoneType: zoneType ? String(zoneType) : void 0,
6286
+ zoneCode: zoneCode ? String(zoneCode) : void 0,
6287
+ legalStatus,
6288
+ planType,
6289
+ planId: planId ? String(planId) : void 0,
6290
+ planDocumentLink: planDocumentLink ? String(planDocumentLink) : void 0,
6291
+ publicationDate: publicationDate ?? void 0,
6292
+ fixingDate: fixingDate ?? void 0,
6293
+ approvalDate: approvalDate ?? void 0,
6294
+ effectiveFrom: effectiveFrom ?? void 0,
6295
+ areaM2: typeof rawArea === "number" ? rawArea : void 0
6296
+ };
6297
+ }
6298
+ function _makeCqlPostFilter(cqlFilter) {
6299
+ const eqMatch = cqlFilter.match(/^(\w+)\s*=\s*'([^']+)'$/);
6300
+ if (eqMatch) {
6301
+ const [, field, value] = eqMatch;
6302
+ return (props) => String(props?.[field] ?? "") === value;
6303
+ }
6304
+ const inMatch = cqlFilter.match(/^(\w+)\s+IN\s*\(([^)]+)\)$/i);
6305
+ if (inMatch) {
6306
+ const [, field, valuesStr] = inMatch;
6307
+ const allowed = new Set(
6308
+ valuesStr.split(",").map((v) => v.trim().replace(/^'|'$/g, ""))
6309
+ );
6310
+ return (props) => allowed.has(String(props?.[field] ?? ""));
6311
+ }
6312
+ return () => true;
6313
+ }
6314
+ var ZurichMapsAdapter = class {
6315
+ categoryMap;
6316
+ baseUrl;
6317
+ sourceId;
6318
+ outputFormat;
6319
+ constructor(options) {
6320
+ this.categoryMap = options.categoryMap;
6321
+ this.baseUrl = options.baseUrl ?? WFS_BASE_URL;
6322
+ this.sourceId = options.sourceId ?? ZURICH_SOURCE_ID;
6323
+ this.outputFormat = options.outputFormat ?? "application/json; subtype=geojson";
6324
+ }
6325
+ async listFeatureCategoryDescriptors(bbox) {
6326
+ const layers = await this.listAvailableLayers(bbox);
6327
+ return [...new Map(layers.map((layer) => [layer.category, featureCategoryDescriptor(layer.category)])).values()];
6328
+ }
6329
+ async listAvailableLayers(bbox) {
6330
+ const entries = Object.entries(this.categoryMap);
6331
+ const results = await Promise.allSettled(
6332
+ entries.flatMap(
6333
+ ([category, layerEntries]) => layerEntries.map(async (entry) => {
6334
+ const typeName = entryTypeName(entry);
6335
+ const cqlFilter = entryCqlFilter(entry);
6336
+ const fc = await this._fetchFeatures(bbox, typeName, 1, cqlFilter);
6337
+ return { category, typeName, cqlFilter, hasResults: fc.features.length > 0 };
6338
+ })
6339
+ )
6340
+ );
6341
+ return results.filter(
6342
+ (r) => r.status === "fulfilled" && r.value.hasResults
6343
+ ).map((r) => toLayerDescriptor2(this.sourceId, r.value.category, r.value.typeName, r.value.cqlFilter));
6344
+ }
6345
+ async getFeaturesForLayer(bbox, layerId) {
6346
+ const found = this._findLayerById(layerId);
6347
+ if (!found)
6348
+ return [];
6349
+ return this._fetchAndConvert(bbox, found.descriptor, found.cqlFilter);
6350
+ }
6351
+ async _fetchAndConvert(bbox, layer, cqlFilter) {
6352
+ const fc = await this._fetchFeatures(bbox, layer.sourceLayerId, void 0, cqlFilter);
6353
+ const postFilter = cqlFilter ? _makeCqlPostFilter(cqlFilter) : void 0;
6354
+ const features = postFilter ? fc.features.filter((f) => postFilter(f.properties)) : fc.features;
6355
+ return features.map((feature, index) => this.toGisFeature(feature, layer, index));
6356
+ }
6357
+ /**
6358
+ * Returns the categories that have at least one configured typename AND at
6359
+ * least one feature in the given bbox (light `count=1` probe per typename).
6360
+ */
6361
+ async listFeatureCategories(bbox) {
6362
+ const layers = await this.listAvailableLayers(bbox);
6363
+ return [...new Set(layers.map((layer) => layer.category))];
6364
+ }
6365
+ async getFeaturesOfCategory(bbox, category) {
6366
+ const entries = this.categoryMap[category] ?? [];
6367
+ const batches = await Promise.all(
6368
+ entries.map((entry) => {
6369
+ const typeName = entryTypeName(entry);
6370
+ const cqlFilter = entryCqlFilter(entry);
6371
+ const layer = toLayerDescriptor2(this.sourceId, category, typeName);
6372
+ return this._fetchAndConvert(bbox, layer, cqlFilter);
6373
+ })
6374
+ );
6375
+ return batches.flat();
6376
+ }
6377
+ // ─── Private helpers ────────────────────────────────────────────────────────
6378
+ _findLayerById(layerId) {
6379
+ const entries = Object.entries(this.categoryMap);
6380
+ for (const [category, layerEntries] of entries) {
6381
+ for (const entry of layerEntries) {
6382
+ const typeName = entryTypeName(entry);
6383
+ const cqlFilter = entryCqlFilter(entry);
6384
+ const layer = toLayerDescriptor2(this.sourceId, category, typeName, cqlFilter);
6385
+ if (layer.id === layerId) {
6386
+ return { descriptor: layer, cqlFilter };
6387
+ }
6388
+ }
6389
+ }
6390
+ return void 0;
6391
+ }
6392
+ async _fetchFeatures(bbox, typeName, count, cqlFilter) {
6393
+ const [west, south, east, north] = bbox;
6394
+ const bboxParam = `${south},${west},${north},${east},urn:ogc:def:crs:EPSG::4326`;
6395
+ const params = new URLSearchParams({
6396
+ service: "WFS",
6397
+ version: "2.0.0",
6398
+ request: "GetFeature",
6399
+ typename: typeName,
6400
+ bbox: bboxParam,
6401
+ srsName: "EPSG:4326",
6402
+ outputFormat: this.outputFormat
6403
+ });
6404
+ if (count !== void 0) {
6405
+ params.set("count", String(count));
6406
+ }
6407
+ if (cqlFilter !== void 0) {
6408
+ params.set("CQL_FILTER", cqlFilter);
6409
+ }
6410
+ params.set("outputFormat", this.outputFormat);
6411
+ const url = `${this.baseUrl}?${params.toString()}`;
6412
+ const response = await fetch(url);
6413
+ if (!response.ok) {
6414
+ throw new Error(
6415
+ `WFS request failed for ${typeName} (${this.sourceId}): ${response.status} ${response.statusText}`
6416
+ );
6417
+ }
6418
+ return response.json();
6419
+ }
6420
+ toGisFeature(feature, layer, index) {
6421
+ const [lon, lat] = firstCoordinate(feature.geometry);
6422
+ const name = pickName(feature.properties, `${layer.sourceLayerId}[${index}]`);
6423
+ const featureBBox = feature.bbox ? [feature.bbox[0], feature.bbox[1], feature.bbox[2], feature.bbox[3]] : void 0;
6424
+ const tier = layer.tier;
6425
+ const properties = tier === "priority" ? this._extractNormalisedProperties(feature, layer) : void 0;
6426
+ return {
6427
+ id: feature.id ?? `${layer.sourceLayerId}/${index}`,
6428
+ preferredColor: layer.preferredColor,
6429
+ sourceId: this.sourceId,
6430
+ sourceFeatureId: feature.id ?? `${layer.sourceLayerId}/${index}`,
6431
+ layerId: layer.id,
6432
+ name,
6433
+ category: layer.category,
6434
+ type: layer.sourceLayerId.replace(/^ms:/, ""),
6435
+ lat,
6436
+ lon,
6437
+ displayName: name,
6438
+ bbox: featureBBox,
6439
+ geometry: feature.geometry ?? void 0,
6440
+ tier,
6441
+ properties,
6442
+ originalData: feature.properties ?? {}
6443
+ };
6444
+ }
6445
+ _extractNormalisedProperties(feature, layer) {
6446
+ if (layer.category === "building") {
6447
+ return toNormalisedBuildingProperties(feature.properties, this.sourceId);
6448
+ }
6449
+ if (layer.category === "greenspace") {
6450
+ return toNormalisedGreenspaceProperties(feature.properties);
6451
+ }
6452
+ if (layer.category === "paved") {
6453
+ return toNormalisedPavedProperties(feature.properties);
6454
+ }
6455
+ if (layer.category === "zone") {
6456
+ return toNormalisedZoneProperties(feature.properties);
6457
+ }
6458
+ return toNormalisedPlotProperties(feature.properties, this.sourceId);
6459
+ }
6460
+ };
6461
+
6462
+ // libs/js/cue-gis/src/lib/danish-cadastre-adapter.ts
6463
+ var DENMARK_BBOX = [8, 54.5, 15.2, 57.8];
6464
+
6465
+ // libs/js/cue-gis/src/lib/cue-sdk-gis-adapter.ts
6466
+ var DEFAULT_DANISH_SDK_CATEGORY_MAP = {
6467
+ building: [
6468
+ { source: "danish-matrikel", typename: "bbr_v001:bygning_current" }
6469
+ ],
6470
+ cadastre: [
6471
+ { source: "danish-matrikel", typename: "mat_v001:lodflade_current" },
6472
+ { source: "danish-matrikel", typename: "mat_v001:samletfastejendom_current" }
6473
+ ]
6474
+ };
6475
+ function firstCoordinate2(geometry) {
6476
+ if (!geometry)
6477
+ return [0, 0];
6478
+ const coords = geometry.coordinates;
6479
+ switch (geometry.type) {
6480
+ case "Point":
6481
+ return coords;
6482
+ case "MultiPoint":
6483
+ case "LineString":
6484
+ return coords[0];
6485
+ case "MultiLineString":
6486
+ case "Polygon":
6487
+ return coords[0][0];
6488
+ case "MultiPolygon":
6489
+ return coords[0][0][0];
6490
+ default:
6491
+ return [0, 0];
6492
+ }
6493
+ }
6494
+ function pickName2(props, fallback) {
6495
+ if (!props)
6496
+ return fallback;
6497
+ for (const key of [
6498
+ "mat:matrikelnummer",
6499
+ "matrikelnummer",
6500
+ "mat:ejerlavsnavn",
6501
+ "ejerlavsnavn",
6502
+ "name",
6503
+ "bezeichnung",
6504
+ "stednavntekst",
6505
+ "title",
6506
+ "label"
6507
+ ]) {
6508
+ if (typeof props[key] === "string" && props[key])
6509
+ return props[key];
6510
+ }
6511
+ return fallback;
6512
+ }
6513
+ function toLayerId3(sourceId, source, typename) {
6514
+ return `${sourceId}:${source}:${typename}`;
6515
+ }
6516
+ var PRIORITY_CATEGORIES2 = /* @__PURE__ */ new Set(["building", "cadastre", "zone"]);
6517
+ function toLayerDescriptor3(sourceId, category, entry) {
6518
+ const shortName = entry.typename.replace(/^[^:]+:/, "");
6519
+ const descriptor = featureCategoryDescriptor(category);
6520
+ const tier = PRIORITY_CATEGORIES2.has(category) ? "priority" : "raw";
6521
+ return {
6522
+ id: toLayerId3(sourceId, entry.source, entry.typename),
6523
+ sourceId,
6524
+ sourceLayerId: entry.typename,
6525
+ category,
6526
+ preferredColor: descriptor.preferredColor,
6527
+ tier,
6528
+ label: entry.label ?? shortName,
6529
+ description: `${entry.source} / ${entry.typename}`,
6530
+ labelKey: `gis.layer.${sourceId}.${entry.source}.${shortName}.label`,
6531
+ descriptionKey: `gis.layer.${sourceId}.${entry.source}.${shortName}.description`
6532
+ };
6533
+ }
6534
+ function _extractNormalizedProperties(props, category) {
6535
+ if (category === "building") {
6536
+ const rawArea2 = props?.["byg041BebyggetAreal"] ?? props?.["byg038SamletBygningsareal"];
6537
+ const buildingUse = props?.["byg021BygningensAnvendelse"];
6538
+ const registryId = props?.["id_lokalId"];
6539
+ const rawYear = props?.["byg026Opf\xF8relses\xE5r"];
6540
+ return {
6541
+ featureType: "building",
6542
+ areaM2: typeof rawArea2 === "number" ? rawArea2 : void 0,
6543
+ buildingUse: buildingUse ?? void 0,
6544
+ yearBuilt: typeof rawYear === "number" ? rawYear : void 0,
6545
+ registryId: registryId ?? void 0
6546
+ };
6547
+ }
6548
+ if (category === "zone") {
6549
+ const zoneType = props?.["plannavn"] ?? props?.["bezeichnung"] ?? props?.["typ_bezeichnung"] ?? void 0;
6550
+ const planId = props?.["planid"] ?? props?.["plannummer"] ?? void 0;
6551
+ const zoneCode = props?.["artcode"] ?? props?.["typ_code"] ?? planId ?? void 0;
6552
+ const rawLegalStatus = props?.["rechtsstatus"] ?? props?.["status"] ?? void 0;
6553
+ const planDocumentLink = props?.["dagsordenpunkt_url"] ?? props?.["dokument_url"] ?? void 0;
6554
+ const rawArea2 = props?.["flaeche"] ?? props?.["flaeche_m2"] ?? void 0;
6555
+ return {
6556
+ featureType: "zone",
6557
+ zoneType: zoneType ?? void 0,
6558
+ zoneCode: zoneCode ?? void 0,
6559
+ legalStatus: classifyZoneLegalStatus(rawLegalStatus),
6560
+ planType: classifyZonePlanType(props?.["typ_gde_bezeichnung"]),
6561
+ planId: planId ?? void 0,
6562
+ planDocumentLink: planDocumentLink ?? void 0,
6563
+ publicationDate: props?.["auflagedatum"] ?? void 0,
6564
+ fixingDate: props?.["festsetzungsdatum"] ?? void 0,
6565
+ approvalDate: props?.["genehmigungsdatum"] ?? void 0,
6566
+ effectiveFrom: props?.["inkraftsetzungsdatum"] ?? void 0,
6567
+ areaM2: typeof rawArea2 === "number" ? rawArea2 : void 0
6568
+ };
6569
+ }
6570
+ const parcelNr = props?.["mat:matrikelnummer"] ?? props?.["matrikelnummer"];
6571
+ const districtName = props?.["mat:ejerlavsnavn"] ?? props?.["ejerlavsnavn"];
6572
+ const rawArea = props?.["mat:registreretareal"] ?? props?.["registreretareal"];
6573
+ const bfeNr = props?.["mat:bfenummer"] ?? props?.["bfenummer"];
6574
+ return {
6575
+ featureType: "plot",
6576
+ areaM2: typeof rawArea === "number" ? rawArea : void 0,
6577
+ plotId: parcelNr && districtName ? `${parcelNr}, ${districtName}` : parcelNr ?? void 0,
6578
+ registryId: bfeNr ?? void 0
6579
+ };
6580
+ }
6581
+ var CueSdkGisAdapter = class {
6582
+ categoryMap;
6583
+ dataViewsBaseUrl;
6584
+ getHeaders;
6585
+ sourceId;
6586
+ constructor(options) {
6587
+ this.dataViewsBaseUrl = options.dataViewsBaseUrl.replace(/\/$/, "");
6588
+ this.getHeaders = options.getHeaders;
6589
+ this.categoryMap = options.categoryMap ?? DEFAULT_DANISH_SDK_CATEGORY_MAP;
6590
+ this.sourceId = options.sourceId ?? "cue-sdk-gis";
6591
+ }
6592
+ async listFeatureCategoryDescriptors(bbox) {
6593
+ const layers = await this.listAvailableLayers(bbox);
6594
+ return [
6595
+ ...new Map(
6596
+ layers.map((l) => [l.category, featureCategoryDescriptor(l.category)])
6597
+ ).values()
6598
+ ];
6599
+ }
6600
+ async listAvailableLayers(bbox) {
6601
+ const entries = Object.entries(this.categoryMap);
6602
+ const results = await Promise.allSettled(
6603
+ entries.flatMap(
6604
+ ([category, layerEntries]) => layerEntries.map(async (entry) => {
6605
+ const fc = await this._fetchFeatures(bbox, entry, 1);
6606
+ return { category, entry, hasResults: fc.features.length > 0 };
6607
+ })
6608
+ )
6609
+ );
6610
+ return results.filter(
6611
+ (r) => r.status === "fulfilled" && r.value.hasResults
6612
+ ).map((r) => toLayerDescriptor3(this.sourceId, r.value.category, r.value.entry));
6613
+ }
6614
+ async getFeaturesForLayer(bbox, layerId) {
6615
+ const result = this._findLayerById(layerId);
6616
+ if (!result)
6617
+ return [];
6618
+ const fc = await this._fetchFeatures(bbox, result.entry);
6619
+ return fc.features.map((f, i) => this._toGisFeature(f, result.descriptor, i));
6620
+ }
6621
+ async listFeatureCategories(bbox) {
6622
+ const layers = await this.listAvailableLayers(bbox);
6623
+ return [...new Set(layers.map((l) => l.category))];
6624
+ }
6625
+ async getFeaturesOfCategory(bbox, category) {
6626
+ const layerEntries = this.categoryMap[category] ?? [];
6627
+ const batches = await Promise.all(
6628
+ layerEntries.map((entry) => {
6629
+ const descriptor = toLayerDescriptor3(this.sourceId, category, entry);
6630
+ return this.getFeaturesForLayer(bbox, descriptor.id);
6631
+ })
6632
+ );
6633
+ return batches.flat();
6634
+ }
6635
+ // ─── Private helpers ────────────────────────────────────────────────────────
6636
+ _findLayerById(layerId) {
6637
+ for (const [category, entries] of Object.entries(this.categoryMap)) {
6638
+ for (const entry of entries) {
6639
+ const descriptor = toLayerDescriptor3(this.sourceId, category, entry);
6640
+ if (descriptor.id === layerId)
6641
+ return { entry, descriptor };
6642
+ }
6643
+ }
6644
+ return void 0;
6645
+ }
6646
+ async _fetchFeatures(bbox, entry, count) {
6647
+ const [west, south, east, north] = bbox;
6648
+ const headers = await this.getHeaders();
6649
+ const params = new URLSearchParams({
6650
+ source: entry.source,
6651
+ typename: entry.typename,
6652
+ bbox: `${west},${south},${east},${north}`
6653
+ });
6654
+ if (count !== void 0) {
6655
+ params.set("count", String(count));
6656
+ }
6657
+ const url = `${this.dataViewsBaseUrl}/gis/features?${params.toString()}`;
6658
+ const response = await fetch(url, { headers });
6659
+ if (!response.ok) {
6660
+ throw new Error(
6661
+ `CueSdkGisAdapter: request failed for ${entry.source}/${entry.typename}: ${response.status} ${response.statusText}`
6662
+ );
6663
+ }
6664
+ return response.json();
6665
+ }
6666
+ _toGisFeature(feature, layer, index) {
6667
+ const [lon, lat] = firstCoordinate2(feature.geometry);
6668
+ const featureId = feature.id ?? `${layer.sourceLayerId}/${index}`;
6669
+ const name = pickName2(feature.properties, featureId);
6670
+ const featureBBox = feature.bbox ? [feature.bbox[0], feature.bbox[1], feature.bbox[2], feature.bbox[3]] : void 0;
6671
+ const tier = layer.tier;
6672
+ const properties = tier === "priority" ? _extractNormalizedProperties(feature.properties, layer.category) : void 0;
6673
+ return {
6674
+ id: featureId,
6675
+ sourceId: this.sourceId,
6676
+ sourceFeatureId: featureId,
6677
+ layerId: layer.id,
6678
+ name,
6679
+ category: layer.category,
6680
+ preferredColor: layer.preferredColor,
6681
+ type: layer.sourceLayerId.replace(/^[^:]+:/, ""),
6682
+ lat,
6683
+ lon,
6684
+ displayName: name,
6685
+ bbox: featureBBox,
6686
+ geometry: feature.geometry ?? void 0,
6687
+ tier,
6688
+ properties,
6689
+ originalData: feature.properties ?? {}
6690
+ };
6691
+ }
6692
+ };
6693
+
6694
+ // libs/js/cue-gis/src/lib/global-config.ts
6695
+ var DEFAULT_ZURICH_CATEGORY_MAP = {
6696
+ address: [
6697
+ "ms:ogd-0406_arv_basis_avzh_hausnummer_pos_p",
6698
+ "ms:ogd-0571_afv_gv_strat_strassennetz_l"
6699
+ ],
6700
+ poi: [
6701
+ "ms:ogd-0053_giszhpub_ogd_veloparkieranlagen_p",
6702
+ "ms:ogd-0406_arv_basis_avzh_gebaeudeeingang_p"
6703
+ ],
6704
+ railway: [
6705
+ "ms:ogd-0529_afv_gv_seilbahn_skilift_l",
6706
+ "ms:ogd-0529_afv_gv_seilbahn_skilift_p"
6707
+ ],
6708
+ natural: [
6709
+ "ms:ogd-0045_giszhpub_wb_fliessgewaesser_l",
6710
+ "ms:ogd-0045_giszhpub_wb_stehgewaesser_f",
6711
+ "ms:ogd-0111_giszhpub_wald_waldareal_f"
6712
+ ],
6713
+ manmade: [
6714
+ "ms:ogd-0248_giszhpub_en_gebaeude_volumen_ha_f",
6715
+ // Transport / infrastructure surface types from the cantonal land-cover layer.
6716
+ { typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('Bahngebiet', 'Strasse_Weg', 'Flugplatz', 'Bruecke')" }
6717
+ ],
6718
+ building: [
6719
+ // Individual building footprint polygons from the official cantonal survey (AV ZH).
6720
+ // BoFlaeche covers all land-cover types; the CQL filter restricts to buildings.
6721
+ { typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art = 'Geb\xE4ude'" }
6722
+ ],
6723
+ greenspace: [
6724
+ // Parks, gardens, playgrounds, sports facilities and other vegetated open areas.
6725
+ { typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('Gartenanlage', 'Parkanlage', 'Spielplatz', 'Sportanlage', 'Friedhof', 'Gehoelz', 'Feuchtgebiet')" }
6726
+ ],
6727
+ paved: [
6728
+ // Sealed / paved ground surfaces (courtyards, sidewalks, parking, etc.).
6729
+ { typeName: "ms:ogd-0401_arv_basis_avzh_bodenbedeckung_f", cqlFilter: "art IN ('befBoden', 'befestigte Fl\xE4che', 'Hof', 'Trottoir', 'Parkplatz')" }
6730
+ ],
6731
+ cadastre: [
6732
+ // Land parcels (Liegenschaften) from the official cantonal survey (AV ZH)
6733
+ "ms:ogd-0404_arv_basis_avzh_liegenschaften_f",
6734
+ // Independent and permanent rights (SelbstRecht / SDR)
6735
+ "ms:ogd-0404_arv_basis_avzh_selbstrecht_f"
6736
+ ],
6737
+ zone: [
6738
+ // ÖREB Nutzungsplanung (Grundnutzung) – primary land-use zoning polygons
6739
+ "ms:ogd-0156_arv_basis_np_gn_zonenflaeche_f",
6740
+ // ÖREB Überlagernde Festlegungen (Flächen) – overlaying planning constraints
6741
+ "ms:ogd-0155_arv_basis_np_ul_flaeche_f"
6742
+ ]
6743
+ };
6744
+ var DEFAULT_LOCAL_SERVICE_REGIONS = [
6745
+ {
6746
+ name: "Canton of Z\xFCrich",
6747
+ coverageBBox: ZURICH_CANTON_BBOX,
6748
+ supportedCategories: ["address", "poi", "railway", "natural", "manmade", "building", "cadastre", "greenspace", "paved", "zone"],
6749
+ priority: 10,
6750
+ adapter: new ZurichMapsAdapter({
6751
+ categoryMap: DEFAULT_ZURICH_CATEGORY_MAP
6752
+ })
6753
+ },
6754
+ {
6755
+ name: "Switzerland (national AV cadastre)",
6756
+ coverageBBox: SWITZERLAND_BBOX,
6757
+ supportedCategories: ["cadastre"],
6758
+ priority: 5,
6759
+ // Lower priority than canton-specific adapters. For ZH, the canton adapter (priority 10) wins.
6760
+ adapter: new ZurichMapsAdapter({
6761
+ sourceId: "swiss-av-wfs",
6762
+ baseUrl: "https://wfs.geodienste.ch/av_0/deu",
6763
+ categoryMap: {
6764
+ cadastre: [
6765
+ "ms:RESF",
6766
+ // Rechtsgültige Liegenschaften (valid parcels, all cantons)
6767
+ "ms:DPRSF"
6768
+ // Selbständige und dauernde Rechte / SDR (all cantons)
6769
+ ]
6770
+ }
6771
+ })
6772
+ },
6773
+ {
6774
+ /**
6775
+ * Denmark planning zones (lokalplan + kommuneplanrammer) from plandata.dk.
6776
+ *
6777
+ * This is a fully open WFS service (no API key required) provided by
6778
+ * Plandata, the Danish national planning data register.
6779
+ *
6780
+ * - `pdk:lokalplan_vedtaget` – adopted local plans (lokalplaner)
6781
+ * - `pdk:kommuneplanramme_vedtaget` – adopted municipal plan frameworks
6782
+ */
6783
+ name: "Denmark (Plandata.dk \u2013 planning zones)",
6784
+ coverageBBox: DENMARK_BBOX,
6785
+ supportedCategories: ["zone"],
6786
+ priority: 10,
6787
+ adapter: new ZurichMapsAdapter({
6788
+ sourceId: "danish-plandata",
6789
+ baseUrl: "https://wfs.plandata.dk/geoserver/pdk/wfs",
6790
+ outputFormat: "application/json",
6791
+ categoryMap: {
6792
+ zone: [
6793
+ "pdk:lokalplan_vedtaget",
6794
+ // Adopted local plans
6795
+ "pdk:kommuneplanramme_vedtaget"
6796
+ // Adopted municipal plan frameworks
6797
+ ]
6798
+ }
6799
+ })
6800
+ }
6801
+ ];
6802
+
6803
+ // libs/js/cue-gis/src/lib/gis-gateway.ts
6804
+ function sortByCanonicalOrder(descriptors) {
6805
+ return [...descriptors].sort(
6806
+ (a5, b) => FEATURE_CATEGORIES.indexOf(a5.category) - FEATURE_CATEGORIES.indexOf(b.category)
6807
+ );
6808
+ }
6809
+ var GisGateway = class {
6810
+ nominatim;
6811
+ regions;
6812
+ /**
6813
+ * Effective zone plan type colour map — {@link ZONE_PLAN_TYPE_COLORS} merged
6814
+ * with any overrides supplied via {@link GisGatewayOptions.zonePlanTypeColors}.
6815
+ */
6816
+ zonePlanTypeColors;
6817
+ constructor(options = {}) {
6818
+ const {
6819
+ regions,
6820
+ useDefaultRegions = true,
6821
+ zonePlanTypeColors,
6822
+ ...nominatimOptions
6823
+ } = options;
6824
+ this.nominatim = new NominatimAdapter(nominatimOptions);
6825
+ const defaultRegions = useDefaultRegions ? DEFAULT_LOCAL_SERVICE_REGIONS : [];
6826
+ this.regions = regions ?? defaultRegions;
6827
+ this.zonePlanTypeColors = { ...ZONE_PLAN_TYPE_COLORS, ...zonePlanTypeColors };
6828
+ }
6829
+ /**
6830
+ * Return the available feature categories together with UI metadata such as
6831
+ * preferred color.
6832
+ *
6833
+ * When the matching regions declare their {@link LocalServiceRegion.supportedCategories}
6834
+ * the result is built from those declarations without network probing.
6835
+ * Otherwise the winning adapter's own probing is used.
6836
+ *
6837
+ * @param bbox [west, south, east, north] in WGS-84 decimal degrees
6838
+ */
6839
+ async listFeatureCategoryDescriptors(bbox) {
6840
+ const declaredRegions = this.regions.filter((r) => bboxIntersects(r.coverageBBox, bbox) && r.supportedCategories).sort((a5, b) => (b.priority ?? 0) - (a5.priority ?? 0));
6841
+ if (declaredRegions.length > 0) {
6842
+ const seen = /* @__PURE__ */ new Set();
6843
+ const descriptors = [];
6844
+ for (const region of declaredRegions) {
6845
+ for (const cat of region.supportedCategories ?? []) {
6846
+ if (!seen.has(cat)) {
6847
+ seen.add(cat);
6848
+ descriptors.push(featureCategoryDescriptor(cat));
6849
+ }
6850
+ }
6851
+ }
6852
+ for (const cat of NOMINATIM_FEATURE_CATEGORIES) {
6853
+ if (!seen.has(cat)) {
6854
+ descriptors.push(featureCategoryDescriptor(cat));
6855
+ }
6856
+ }
6857
+ return sortByCanonicalOrder(descriptors);
6858
+ }
6859
+ const probed = await this._adapterFor(bbox).listFeatureCategoryDescriptors(bbox);
6860
+ return sortByCanonicalOrder(probed);
6861
+ }
6862
+ /**
6863
+ * Return the feature categories that have at least one result within the
6864
+ * given bounding box.
6865
+ *
6866
+ * @param bbox [west, south, east, north] in WGS-84 decimal degrees
6867
+ */
6868
+ async listFeatureCategories(bbox) {
6869
+ const descriptors = await this.listFeatureCategoryDescriptors(bbox);
6870
+ return descriptors.map((d) => d.category);
6871
+ }
6872
+ /**
6873
+ * Return all available layers for the given bbox using a provider-independent
6874
+ * layer descriptor shape.
6875
+ */
6876
+ async listAvailableLayers(bbox) {
6877
+ return this._adapterFor(bbox).listAvailableLayers(bbox);
6878
+ }
6879
+ /**
6880
+ * Stream GIS features of the given category from **all** services whose
6881
+ * coverage intersects the query bbox (plus the Nominatim global fallback).
6882
+ *
6883
+ * Processing steps:
6884
+ * 1. Log all configured services and their coverage bboxes.
6885
+ * 2. Identify services whose `coverageBBox` intersects `bbox`.
6886
+ * 3. Log the matched services.
6887
+ * 4. Fan out requests to all matched services in parallel; yield features as
6888
+ * each source resolves (streaming).
6889
+ * 5. All features are already normalised to {@link GisFeature} by each adapter.
6890
+ * 6. Quality-based deduplication: when two sources return a feature for the
6891
+ * same real-world object, only the higher-quality geometry is emitted.
6892
+ * A polygon/multipolygon beats a linestring, which beats a point. If a
6893
+ * better feature arrives after a lower-quality one was already yielded,
6894
+ * the better feature is yielded again (same {@link GisFeature.id}) so the
6895
+ * consumer can replace its copy.
6896
+ *
6897
+ * @param bbox [west, south, east, north] in WGS-84 decimal degrees
6898
+ * @param category One of the categories returned by {@link listFeatureCategories}
6899
+ */
6900
+ async *streamFeaturesOfCategory(bbox, category) {
6901
+ console.log("[GisGateway] Available services:");
6902
+ for (const region of this.regions) {
6903
+ const [w, s, e, n] = region.coverageBBox;
6904
+ console.log(
6905
+ ` \u2022 ${region.name} bbox=[${w}, ${s}, ${e}, ${n}] priority=${region.priority ?? 0}`
6906
+ );
6907
+ }
6908
+ console.log(" \u2022 Nominatim (OSM) bbox=[global] priority=-1");
6909
+ const matchedRegions = this.regions.filter(
6910
+ (r) => bboxIntersects(r.coverageBBox, bbox) && (!r.supportedCategories || r.supportedCategories.includes(category))
6911
+ );
6912
+ const sources = [
6913
+ ...matchedRegions.map((r) => ({
6914
+ name: r.name,
6915
+ adapter: r.adapter,
6916
+ priority: r.priority ?? 0
6917
+ })),
6918
+ { name: "Nominatim (OSM)", adapter: this.nominatim, priority: -1 }
6919
+ ];
6920
+ sources.sort((a5, b) => b.priority - a5.priority);
6921
+ console.log(`[GisGateway] Matched services for category "${category}":`);
6922
+ for (const s of sources) {
6923
+ console.log(` \u2022 ${s.name} (priority=${s.priority})`);
6924
+ }
6925
+ const qualityMap = /* @__PURE__ */ new Map();
6926
+ const remaining = new Map(
6927
+ sources.map(
6928
+ (s, i) => [
6929
+ i,
6930
+ s.adapter.getFeaturesOfCategory(bbox, category).then((features) => ({ features, idx: i })).catch((err) => {
6931
+ console.warn(`[GisGateway] Source "${s.name}" failed:`, err);
6932
+ return { features: [], idx: i };
6933
+ })
6934
+ ]
6935
+ )
6936
+ );
6937
+ while (remaining.size > 0) {
6938
+ const { features, idx } = await Promise.race(remaining.values());
6939
+ remaining.delete(idx);
6940
+ for (const feature of features) {
6941
+ const key = _dedupKey(feature);
6942
+ const score = _geometryQuality(feature);
6943
+ const best = qualityMap.get(key);
6944
+ if (best === void 0 || score > best) {
6945
+ qualityMap.set(key, score);
6946
+ yield feature;
6947
+ }
6948
+ }
6949
+ }
6950
+ }
6951
+ /**
6952
+ * Return all GIS features of the given category within the bounding box.
6953
+ *
6954
+ * Fans out to all matching services in parallel, then applies:
6955
+ * 1. Spatial merging – Nominatim point features that fall within ~100 m of a
6956
+ * local-service polygon are merged into that polygon (enriching its
6957
+ * `originalData`) rather than returned as a separate feature.
6958
+ * 2. Quality-based deduplication – when two unmerged features represent the
6959
+ * same object, the one with richer geometry is kept.
6960
+ *
6961
+ * @param bbox [west, south, east, north] in WGS-84 decimal degrees
6962
+ * @param category One of the categories returned by {@link listFeatureCategories}
6963
+ */
6964
+ async getFeaturesOfCategory(bbox, category) {
6965
+ const matchedRegions = this.regions.filter(
6966
+ (r) => bboxIntersects(r.coverageBBox, bbox) && (!r.supportedCategories || r.supportedCategories.includes(category))
6967
+ );
6968
+ const sources = [
6969
+ ...matchedRegions.map((r) => ({ name: r.name, adapter: r.adapter, isNominatim: false })),
6970
+ { name: "Nominatim (OSM)", adapter: this.nominatim, isNominatim: true }
6971
+ ];
6972
+ const batches = await Promise.allSettled(
6973
+ sources.map(
6974
+ (s) => s.adapter.getFeaturesOfCategory(bbox, category).then((features) => ({
6975
+ features,
6976
+ isNominatim: s.isNominatim
6977
+ })).catch((err) => {
6978
+ console.warn(`[GisGateway] Source "${s.name}" failed:`, err);
6979
+ return { features: [], isNominatim: s.isNominatim };
6980
+ })
6981
+ )
6982
+ );
6983
+ const localFeatures = [];
6984
+ const nominatimFeatures = [];
6985
+ for (const result of batches) {
6986
+ if (result.status !== "fulfilled")
6987
+ continue;
6988
+ if (result.value.isNominatim) {
6989
+ nominatimFeatures.push(...result.value.features);
6990
+ } else {
6991
+ localFeatures.push(...result.value.features);
6992
+ }
6993
+ }
6994
+ const merged = _mergeNominatimIntoPolygons(localFeatures, nominatimFeatures);
6995
+ const qualityMap = /* @__PURE__ */ new Map();
6996
+ const deduplicated = [];
6997
+ for (const feature of merged) {
6998
+ const key = _dedupKey(feature);
6999
+ const score = _geometryQuality(feature);
7000
+ const best = qualityMap.get(key);
7001
+ if (best === void 0 || score > best) {
7002
+ qualityMap.set(key, score);
7003
+ deduplicated.push(feature);
7004
+ }
7005
+ }
7006
+ return deduplicated;
7007
+ }
7008
+ /** Fetch features for a specific layer descriptor id. */
7009
+ async getFeaturesForLayer(bbox, layerId) {
7010
+ return this._adapterFor(bbox).getFeaturesForLayer(bbox, layerId);
7011
+ }
7012
+ /** Resolve the best adapter for a query bbox (legacy single-adapter routing). */
7013
+ _adapterFor(bbox) {
7014
+ const region = this.regions.filter((r) => bboxIntersects(r.coverageBBox, bbox)).sort((a5, b) => (b.priority ?? 0) - (a5.priority ?? 0))[0];
7015
+ return region?.adapter ?? this.nominatim;
7016
+ }
7017
+ };
7018
+ function _geometryQuality(feature) {
7019
+ const gt = feature.geometry?.type;
7020
+ if (gt === "Polygon" || gt === "MultiPolygon")
7021
+ return 3;
7022
+ if (gt === "LineString" || gt === "MultiLineString")
7023
+ return 2;
7024
+ return 1;
7025
+ }
7026
+ function _dedupKey(feature) {
7027
+ const lat = Math.round(feature.lat * 1e3) / 1e3;
7028
+ const lon = Math.round(feature.lon * 1e3) / 1e3;
7029
+ const name = (feature.name ?? feature.id ?? "").toLowerCase().trim();
7030
+ return `${name}|${lat}|${lon}`;
7031
+ }
7032
+ var MERGE_THRESHOLD = 1e-3;
7033
+ function _latLonDist(a5, b) {
7034
+ const dlat = a5.lat - b.lat;
7035
+ const dlon = a5.lon - b.lon;
7036
+ return Math.sqrt(dlat * dlat + dlon * dlon);
7037
+ }
7038
+ function _mergeNominatimIntoPolygons(localFeatures, nominatimFeatures) {
7039
+ if (nominatimFeatures.length === 0)
7040
+ return localFeatures;
7041
+ const polygons = localFeatures.filter((f) => _geometryQuality(f) === 3);
7042
+ const nonPolygons = localFeatures.filter((f) => _geometryQuality(f) !== 3);
7043
+ const enriched = polygons.map((f) => ({ feature: { ...f, originalData: { ...f.originalData } }, matched: false }));
7044
+ const unmatched = [];
7045
+ for (const nom of nominatimFeatures) {
7046
+ let bestIdx = -1;
7047
+ let bestDist = MERGE_THRESHOLD;
7048
+ for (let i = 0; i < enriched.length; i++) {
7049
+ const dist = _latLonDist(nom, enriched[i].feature);
7050
+ if (dist < bestDist) {
7051
+ bestDist = dist;
7052
+ bestIdx = i;
7053
+ }
7054
+ }
7055
+ if (bestIdx >= 0) {
7056
+ enriched[bestIdx].feature.originalData = {
7057
+ ...nom.originalData,
7058
+ ...enriched[bestIdx].feature.originalData
7059
+ };
7060
+ enriched[bestIdx].matched = true;
7061
+ } else {
7062
+ unmatched.push(nom);
7063
+ }
7064
+ }
7065
+ return [
7066
+ ...enriched.map((e) => e.feature),
7067
+ ...nonPolygons,
7068
+ ...unmatched
7069
+ ];
7070
+ }
7071
+
7072
+ // libs/js/cue-sdk/src/lib/gis.ts
7073
+ var DATA_VIEWS_PATH = "/data-views";
7074
+ var DEBOUNCE_MS = 1500;
7075
+ var CueGis = class {
7076
+ // undefined = not yet built
7077
+ /** @internal — construct via `cue.gis`, not directly. */
7078
+ constructor(_getAuthHeaders, _gatewayUrl) {
7079
+ this._getAuthHeaders = _getAuthHeaders;
7080
+ this._gatewayUrl = _gatewayUrl;
7081
+ }
7082
+ _bbox = null;
7083
+ _projectId = null;
7084
+ _selectedCategories = /* @__PURE__ */ new Set();
7085
+ _queryToken = 0;
7086
+ _categoryTokens = /* @__PURE__ */ new Map();
7087
+ _debounceTimer = null;
7088
+ _availableCategories = [];
7089
+ _features = /* @__PURE__ */ new Map();
7090
+ _catListeners = /* @__PURE__ */ new Set();
7091
+ _featListeners = /* @__PURE__ */ new Set();
7092
+ _loadListeners = /* @__PURE__ */ new Set();
7093
+ _gatewayCache = null;
7094
+ _gatewayProjectId = void 0;
7095
+ // ── Setters ───────────────────────────────────────────────────────────────
7096
+ /**
7097
+ * Update the current map viewport.
7098
+ * Triggers a debounced query for available categories and reloads selected ones.
7099
+ */
7100
+ setBbox(bbox) {
7101
+ this._bbox = bbox;
7102
+ this._scheduleDebouncedQuery();
7103
+ }
7104
+ /**
7105
+ * Set or clear the active project.
7106
+ * Rebuilds the authenticated adapter so subsequent requests carry the new project header.
7107
+ */
7108
+ setProjectId(projectId) {
7109
+ if (projectId === this._projectId)
7110
+ return;
7111
+ this._projectId = projectId;
7112
+ this._gatewayCache = null;
7113
+ this._scheduleDebouncedQuery();
7114
+ }
7115
+ /**
7116
+ * Replace the full set of selected categories.
7117
+ * Newly added categories begin loading immediately; removed categories are cleared.
7118
+ */
7119
+ setSelectedCategories(categories) {
7120
+ const prev = this._selectedCategories;
7121
+ this._selectedCategories = new Set(categories);
7122
+ let featuresChanged = false;
7123
+ for (const cat of prev) {
7124
+ if (!categories.has(cat)) {
7125
+ this._features.delete(cat);
7126
+ featuresChanged = true;
7127
+ }
7128
+ }
7129
+ if (featuresChanged)
7130
+ this._emitFeatures();
7131
+ const bbox = this._bbox;
7132
+ if (bbox) {
7133
+ for (const cat of categories) {
7134
+ if (!prev.has(cat))
7135
+ this._loadCategory(cat, bbox, this._queryToken);
7136
+ }
7137
+ }
7138
+ }
7139
+ // ── Subscriptions ─────────────────────────────────────────────────────────
7140
+ /**
7141
+ * Subscribe to available-category updates.
7142
+ * Replays the current value immediately, then fires on every bbox change.
7143
+ * @returns An unsubscribe function.
7144
+ */
7145
+ onAvailableCategories(cb) {
7146
+ cb([...this._availableCategories]);
7147
+ this._catListeners.add(cb);
7148
+ return () => this._catListeners.delete(cb);
7149
+ }
7150
+ /**
7151
+ * Subscribe to the feature map (category → GisFeature[]).
7152
+ * Replays the current value immediately, then fires whenever features change.
7153
+ * @returns An unsubscribe function.
7154
+ */
7155
+ onFeaturesChange(cb) {
7156
+ cb(new Map(this._features));
7157
+ this._featListeners.add(cb);
7158
+ return () => this._featListeners.delete(cb);
7159
+ }
7160
+ /**
7161
+ * Subscribe to the global loading state.
7162
+ * Fires `true` while the category list is being queried, `false` when done.
7163
+ * @returns An unsubscribe function.
7164
+ */
7165
+ onLoadingChange(cb) {
7166
+ this._loadListeners.add(cb);
7167
+ return () => this._loadListeners.delete(cb);
7168
+ }
7169
+ /** Cancel all pending requests and clear all listeners. */
7170
+ destroy() {
7171
+ if (this._debounceTimer !== null)
7172
+ clearTimeout(this._debounceTimer);
7173
+ this._queryToken++;
7174
+ this._catListeners.clear();
7175
+ this._featListeners.clear();
7176
+ this._loadListeners.clear();
7177
+ }
7178
+ // ── Private ───────────────────────────────────────────────────────────────
7179
+ _scheduleDebouncedQuery() {
7180
+ if (this._debounceTimer !== null)
7181
+ clearTimeout(this._debounceTimer);
7182
+ this._debounceTimer = setTimeout(() => this._queryLayers(), DEBOUNCE_MS);
7183
+ }
7184
+ _getGateway() {
7185
+ if (this._gatewayCache !== null && this._gatewayProjectId === this._projectId) {
7186
+ return this._gatewayCache;
7187
+ }
7188
+ const projectId = this._projectId;
7189
+ const regions = [...DEFAULT_LOCAL_SERVICE_REGIONS];
7190
+ if (projectId) {
7191
+ regions.unshift({
7192
+ name: "Cue SDK (authenticated)",
7193
+ coverageBBox: DENMARK_BBOX,
7194
+ supportedCategories: Object.keys(DEFAULT_DANISH_SDK_CATEGORY_MAP),
7195
+ priority: 10,
7196
+ adapter: new CueSdkGisAdapter({
7197
+ dataViewsBaseUrl: `${this._gatewayUrl}${DATA_VIEWS_PATH}`,
7198
+ getHeaders: async () => ({
7199
+ ...await this._getAuthHeaders(),
7200
+ "x-project-id": projectId
7201
+ })
7202
+ })
7203
+ });
7204
+ }
7205
+ this._gatewayCache = new GisGateway({ regions });
7206
+ this._gatewayProjectId = projectId;
7207
+ return this._gatewayCache;
7208
+ }
7209
+ async _queryLayers() {
7210
+ const bbox = this._bbox;
7211
+ if (!bbox)
7212
+ return;
7213
+ this._queryToken++;
7214
+ const token = this._queryToken;
7215
+ this._emitLoading(true);
7216
+ try {
7217
+ const descriptors = await this._getGateway().listFeatureCategoryDescriptors(bbox);
7218
+ if (token !== this._queryToken)
7219
+ return;
7220
+ this._availableCategories = descriptors;
7221
+ this._catListeners.forEach((cb) => cb([...descriptors]));
7222
+ const available = new Set(descriptors.map((d) => d.category));
7223
+ let changed = false;
7224
+ for (const cat of [...this._selectedCategories]) {
7225
+ if (!available.has(cat)) {
7226
+ this._selectedCategories.delete(cat);
7227
+ this._features.delete(cat);
7228
+ changed = true;
7229
+ }
7230
+ }
7231
+ if (changed)
7232
+ this._emitFeatures();
7233
+ for (const cat of this._selectedCategories) {
7234
+ this._loadCategory(cat, bbox, token);
7235
+ }
7236
+ } catch (err) {
7237
+ console.error("[CueGis] Failed to list categories:", err);
7238
+ } finally {
7239
+ if (token === this._queryToken)
7240
+ this._emitLoading(false);
7241
+ }
7242
+ }
7243
+ async _loadCategory(category, bbox, ownerToken) {
7244
+ const catToken = (this._categoryTokens.get(category) ?? 0) + 1;
7245
+ this._categoryTokens.set(category, catToken);
7246
+ try {
7247
+ const features = await this._getGateway().getFeaturesOfCategory(bbox, category);
7248
+ const stale = this._categoryTokens.get(category) !== catToken || ownerToken !== this._queryToken || !this._selectedCategories.has(category);
7249
+ if (stale)
7250
+ return;
7251
+ this._features.set(category, features);
7252
+ this._emitFeatures();
7253
+ } catch (err) {
7254
+ console.error(`[CueGis] Failed to load "${category}":`, err);
7255
+ }
7256
+ }
7257
+ _emitFeatures() {
7258
+ const snapshot = new Map(this._features);
7259
+ this._featListeners.forEach((cb) => cb(snapshot));
7260
+ }
7261
+ _emitLoading(v) {
7262
+ this._loadListeners.forEach((cb) => cb(v));
7263
+ }
7264
+ };
7265
+
5364
7266
  // libs/js/cue-sdk/src/lib/project.ts
5365
7267
  var import_firestore3 = require("firebase/firestore");
5366
7268
  var import_functions2 = require("firebase/functions");
@@ -5549,7 +7451,7 @@ var CueProfile = class {
5549
7451
  }
5550
7452
  /** Creates a new API key for the current user. */
5551
7453
  async createAPIKey(expiration) {
5552
- return this._fetch(ENDPOINT_PROFILE_API_KEYS, {
7454
+ return this._fetch(ENDPOINT_COMMANDS_PROFILE_API_KEYS, {
5553
7455
  method: "POST",
5554
7456
  headers: { "Content-Type": "application/json" },
5555
7457
  body: JSON.stringify({ expiration })
@@ -5558,7 +7460,7 @@ var CueProfile = class {
5558
7460
  /** Revokes the current user's API key. */
5559
7461
  async revokeAPIKey() {
5560
7462
  const response = await this._auth.authenticatedFetch(
5561
- this._url(ENDPOINT_PROFILE_API_KEYS),
7463
+ this._url(ENDPOINT_COMMANDS_PROFILE_API_KEYS),
5562
7464
  { method: "DELETE" }
5563
7465
  );
5564
7466
  if (!response.ok) {
@@ -5595,10 +7497,26 @@ var CueProfile = class {
5595
7497
  const res = await fn({ uids });
5596
7498
  return res.data;
5597
7499
  }
5598
- /** Record that the current user has accepted the terms of service. */
5599
- async acceptTerms() {
5600
- const fn = (0, import_functions3.httpsCallable)(this._functions, "acceptTerms");
5601
- await fn({});
7500
+ /** Record that the current user has accepted the terms of service. Sets a `terms` custom claim on the token. */
7501
+ async acceptTerms(version) {
7502
+ await this._fetch(ENDPOINT_COMMANDS_PROFILE_TERMS, {
7503
+ method: "POST",
7504
+ headers: { "Content-Type": "application/json" },
7505
+ body: JSON.stringify({ version })
7506
+ });
7507
+ }
7508
+ /**
7509
+ * Returns the terms version the current user has accepted (e.g. `"v1"`),
7510
+ * or `null` if they have not accepted any version yet.
7511
+ * Reads from the cached ID token — call after `acceptTerms()` with a
7512
+ * force-refreshed token to see the updated value.
7513
+ */
7514
+ async latestTermsAccepted() {
7515
+ const user = this._auth.currentUser;
7516
+ if (!user)
7517
+ return null;
7518
+ const result = await (0, import_auth4.getIdTokenResult)(user);
7519
+ return result.claims["terms"] ?? null;
5602
7520
  }
5603
7521
  };
5604
7522
 
@@ -5785,19 +7703,20 @@ var CueProjectSchema = class {
5785
7703
  this._projectId = _projectId;
5786
7704
  this._queryCache = _queryCache;
5787
7705
  this._graphType = _graphType;
5788
- this._language = new CueSignal(language);
7706
+ this._currentLang = language;
7707
+ this._api.setLanguage(language);
5789
7708
  this._contentCategories = new CueSignal([]);
5790
7709
  this._entityCategories = new CueSignal([]);
5791
7710
  this._relationships = new CueSignal([]);
5792
7711
  this.availableContentCategories = this._contentCategories.asReadonly();
5793
7712
  this.availableEntityCategories = this._entityCategories.asReadonly();
5794
7713
  this.availableEntityRelationships = this._relationships.asReadonly();
5795
- this._load(language).catch(
7714
+ this.ready = this._load(language).catch(
5796
7715
  (err) => console.error("[CueProjectSchema] Initial load failed:", err)
5797
7716
  );
5798
7717
  }
5799
7718
  _cache = /* @__PURE__ */ new Map();
5800
- _language;
7719
+ _currentLang;
5801
7720
  _contentCategories;
5802
7721
  _entityCategories;
5803
7722
  _relationships;
@@ -5807,9 +7726,14 @@ var CueProjectSchema = class {
5807
7726
  availableEntityCategories;
5808
7727
  /** Currently active entity relationship types for the selected language. */
5809
7728
  availableEntityRelationships;
7729
+ /**
7730
+ * Resolves when the initial schema load for the constructor language has
7731
+ * completed (or failed). Await this before reading signal values imperatively.
7732
+ */
7733
+ ready;
5810
7734
  /** Returns the currently active language. */
5811
7735
  get language() {
5812
- return this._language.get();
7736
+ return this._api.language;
5813
7737
  }
5814
7738
  /**
5815
7739
  * Switch the active language. If the data for this language has already been
@@ -5817,9 +7741,10 @@ var CueProjectSchema = class {
5817
7741
  * is triggered.
5818
7742
  */
5819
7743
  setLanguage(lang) {
5820
- if (this._language.get() === lang)
7744
+ if (this._currentLang === lang)
5821
7745
  return;
5822
- this._language.set(lang);
7746
+ this._currentLang = lang;
7747
+ this._api.setLanguage(lang);
5823
7748
  this._load(lang).catch(
5824
7749
  (err) => console.error("[CueProjectSchema] Language switch failed:", err)
5825
7750
  );
@@ -5829,7 +7754,7 @@ var CueProjectSchema = class {
5829
7754
  * Useful when the triplestore data has changed.
5830
7755
  */
5831
7756
  async refresh() {
5832
- const lang = this._language.get();
7757
+ const lang = this._api.language;
5833
7758
  this._cache.delete(lang);
5834
7759
  await this._load(lang);
5835
7760
  }
@@ -5855,7 +7780,7 @@ var CueProjectSchema = class {
5855
7780
  },
5856
7781
  (snapshot) => {
5857
7782
  this._cache.set(lang, snapshot);
5858
- if (this._language.get() === lang)
7783
+ if (this._currentLang === lang)
5859
7784
  this._apply(snapshot);
5860
7785
  },
5861
7786
  this._queryCache
@@ -5944,7 +7869,9 @@ var CueProjectEntities = class {
5944
7869
  baseURL;
5945
7870
  // ── Internal writable slices ───────────────────────────────────────────────
5946
7871
  _entityDetails = new CueSignal({});
5947
- _entityDocuments = new CueSignal({});
7872
+ _entityDocuments = new CueSignal(
7873
+ {}
7874
+ );
5948
7875
  _entityRelationships = new CueSignal({});
5949
7876
  _entityOSMMap = new CueSignal({});
5950
7877
  _osmWKTMap = new CueSignal({});
@@ -5986,7 +7913,10 @@ var CueProjectEntities = class {
5986
7913
  this._entityGraph.set(void 0);
5987
7914
  this._fetchingOSMIds.clear();
5988
7915
  this._fetchEntityGraph().catch(
5989
- (err) => console.error("[CueProjectEntities] Entity graph fetch failed after reset:", err)
7916
+ (err) => console.error(
7917
+ "[CueProjectEntities] Entity graph fetch failed after reset:",
7918
+ err
7919
+ )
5990
7920
  );
5991
7921
  }
5992
7922
  // ── Public imperative API ──────────────────────────────────────────────────
@@ -5997,7 +7927,9 @@ var CueProjectEntities = class {
5997
7927
  * Data is merged into `entityInfoMap` once the SPARQL response arrives.
5998
7928
  */
5999
7929
  requestEntityData(uuids, includeMentionCount = false) {
6000
- const newUUIDs = uuids.filter((id) => this._entityDetails.get()[id] === void 0);
7930
+ const newUUIDs = uuids.filter(
7931
+ (id) => this._entityDetails.get()[id] === void 0
7932
+ );
6001
7933
  if (newUUIDs.length === 0)
6002
7934
  return;
6003
7935
  const values = newUUIDs.map((id) => `r:${id}`).join(" ");
@@ -6015,7 +7947,9 @@ WHERE {
6015
7947
  GROUP BY ?id ?mentionCount`;
6016
7948
  this._api.sparql(q, this._projectId, this._graphType).then((data) => {
6017
7949
  const result = data;
6018
- const updates = { ...this._entityDetails.get() };
7950
+ const updates = {
7951
+ ...this._entityDetails.get()
7952
+ };
6019
7953
  result.results.bindings.forEach((b) => {
6020
7954
  if (!b["id"])
6021
7955
  return;
@@ -6039,7 +7973,9 @@ GROUP BY ?id ?mentionCount`;
6039
7973
  * query and merged into `entityInfoMap` reactively once it arrives.
6040
7974
  */
6041
7975
  async requestEntityLocations(uuids) {
6042
- const newUUIDs = uuids.filter((id) => this._entityOSMMap.get()[id] === void 0);
7976
+ const newUUIDs = uuids.filter(
7977
+ (id) => this._entityOSMMap.get()[id] === void 0
7978
+ );
6043
7979
  if (newUUIDs.length === 0)
6044
7980
  return;
6045
7981
  const osmInit = { ...this._entityOSMMap.get() };
@@ -6075,7 +8011,11 @@ WHERE {
6075
8011
  }
6076
8012
  BIND(REPLACE(STR(?iri), "^.*/([^/]*)$", "$1") AS ?id)
6077
8013
  }`;
6078
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8014
+ const data = await this._api.sparql(
8015
+ q,
8016
+ this._projectId,
8017
+ this._graphType
8018
+ );
6079
8019
  const update = { ...this._entityOSMMap.get() };
6080
8020
  data.results.bindings.forEach((b) => {
6081
8021
  if (!b["id"] || !b["osm"])
@@ -6114,7 +8054,10 @@ WHERE {
6114
8054
  this._fetchIncomingRelationships(iri)
6115
8055
  ]);
6116
8056
  const result = { outgoing, incoming };
6117
- this._entityRelationships.set({ ...this._entityRelationships.get(), [uuid]: result });
8057
+ this._entityRelationships.set({
8058
+ ...this._entityRelationships.get(),
8059
+ [uuid]: result
8060
+ });
6118
8061
  return result;
6119
8062
  }
6120
8063
  /**
@@ -6138,11 +8081,111 @@ WHERE {
6138
8081
  qcy:about ?iri .
6139
8082
  BIND(REPLACE(STR(?doc), "^.*/([^/]*)$", "$1") AS ?id)
6140
8083
  }`;
6141
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8084
+ const data = await this._api.sparql(
8085
+ q,
8086
+ this._projectId,
8087
+ this._graphType
8088
+ );
6142
8089
  const ids = data.results.bindings.filter((b) => b["id"] !== void 0).map((b) => b["id"].value);
6143
8090
  this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: ids });
6144
8091
  return ids;
6145
8092
  }
8093
+ /**
8094
+ * Fetches all `qcy:EntityCategory` IRIs and their preferred labels for this
8095
+ * project. Uses `api.language` (default `'en'`);
8096
+ * falls back to an untagged label when no match is found.
8097
+ */
8098
+ async contentCategoriesInProject(orderByOccurences = true) {
8099
+ const language = this._api.language;
8100
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8101
+ PREFIX skos: <${prefixCC["skos"]}>
8102
+ SELECT ?iri (SAMPLE(?l) AS ?label) ?count
8103
+ WHERE {
8104
+ ?iri a qcy:EntityCategory .
8105
+ OPTIONAL { ?iri skos:prefLabel ?lang_label FILTER(LANG(?lang_label) = "${language}") }
8106
+ OPTIONAL { ?iri skos:prefLabel ?no_lang_label }
8107
+ BIND(COALESCE(?lang_label, ?no_lang_label) AS ?l)
8108
+ ${orderByOccurences ? "{ SELECT (COUNT(?x) AS ?count) ?iri where { ?x a qcy:CanonicalEntity ; qcy:hasEntityCategory ?iri } GROUP BY ?iri }" : ""}
8109
+ }
8110
+ GROUP BY ?iri ?count
8111
+ ORDER BY ${orderByOccurences ? "DESC(?count)" : "ASC(?label)"}`;
8112
+ const data = await this._api.sparql(
8113
+ q,
8114
+ this._projectId,
8115
+ this._graphType
8116
+ );
8117
+ return data.results.bindings.filter((b) => b["iri"] !== void 0).map((b) => {
8118
+ const count = b["count"] ? parseInt(b["count"].value, 10) : 0;
8119
+ const label = b["label"]?.value ?? b["iri"].value.split("#").at(-1);
8120
+ const labelWithCount = count > 0 ? `${label} (${count})` : label ?? "";
8121
+ return {
8122
+ iri: b["iri"].value,
8123
+ label: labelWithCount
8124
+ };
8125
+ });
8126
+ }
8127
+ async buildSummaryGraph(format) {
8128
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8129
+ SELECT
8130
+ ?sourceCat
8131
+ ?predicate
8132
+ ?targetCat
8133
+ (COUNT(*) AS ?weight)
8134
+ WHERE {
8135
+ ?s a qcy:CanonicalEntity ;
8136
+ qcy:hasEntityCategory ?sourceCat .
8137
+ ?o a qcy:CanonicalEntity ;
8138
+ qcy:hasEntityCategory ?targetCat .
8139
+ ?s ?predicate ?o .
8140
+ FILTER(isIRI(?s) && isIRI(?o))
8141
+ FILTER(?predicate != qcy:relatedEntity)
8142
+ }
8143
+ GROUP BY ?sourceCat ?predicate ?targetCat
8144
+ ORDER BY DESC(?weight)`;
8145
+ const data = await this._api.sparql(
8146
+ q,
8147
+ this._projectId,
8148
+ this._graphType
8149
+ );
8150
+ const bindings = data.results.bindings.filter(
8151
+ (b) => b["sourceCat"] && b["predicate"] && b["targetCat"]
8152
+ );
8153
+ if (format === "graph") {
8154
+ const irisSet = /* @__PURE__ */ new Set();
8155
+ const relations = [];
8156
+ for (const b of bindings) {
8157
+ const sourceID = b["sourceCat"].value;
8158
+ const predicate = b["predicate"].value;
8159
+ const targetID = b["targetCat"].value;
8160
+ const weight = parseInt(b["weight"].value, 10);
8161
+ irisSet.add(sourceID);
8162
+ irisSet.add(targetID);
8163
+ relations.push({ sourceID, predicate, targetID, weight });
8164
+ }
8165
+ return {
8166
+ entities: Array.from(irisSet).map((iri) => ({ iri })),
8167
+ relations
8168
+ };
8169
+ }
8170
+ if (format === "md") {
8171
+ const ce = CompactExpand.getInstance();
8172
+ const rows = bindings.map((b) => ({
8173
+ src: ce.compactIRI(b["sourceCat"].value),
8174
+ pred: ce.compactIRI(b["predicate"].value),
8175
+ tgt: ce.compactIRI(b["targetCat"].value),
8176
+ weight: parseInt(b["weight"].value, 10)
8177
+ }));
8178
+ if (rows.length === 0)
8179
+ return "(no results)";
8180
+ const maxSrc = Math.max(...rows.map((r) => r.src.length));
8181
+ const maxPred = Math.max(...rows.map((r) => r.pred.length));
8182
+ const maxTgt = Math.max(...rows.map((r) => r.tgt.length));
8183
+ return rows.map(
8184
+ (r) => `${r.src.padEnd(maxSrc)} -> ${r.pred.padEnd(maxPred)} -> ${r.tgt.padEnd(maxTgt)} (${r.weight})`
8185
+ ).join("\n");
8186
+ }
8187
+ return data;
8188
+ }
6146
8189
  // ── Private helpers ────────────────────────────────────────────────────────
6147
8190
  _computeEntityInfoMap() {
6148
8191
  const details = this._entityDetails.get();
@@ -6216,7 +8259,11 @@ WHERE {
6216
8259
  FILTER(?rel != qcy:relatedEntity)
6217
8260
  }
6218
8261
  GROUP BY ?rel ?related`;
6219
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8262
+ const data = await this._api.sparql(
8263
+ q,
8264
+ this._projectId,
8265
+ this._graphType
8266
+ );
6220
8267
  const result = [];
6221
8268
  const detailsUpdate = { ...this._entityDetails.get() };
6222
8269
  data.results.bindings.forEach((b) => {
@@ -6250,7 +8297,11 @@ WHERE {
6250
8297
  FILTER(?rel != qcy:relatedEntity)
6251
8298
  }
6252
8299
  GROUP BY ?rel ?relating`;
6253
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8300
+ const data = await this._api.sparql(
8301
+ q,
8302
+ this._projectId,
8303
+ this._graphType
8304
+ );
6254
8305
  const result = [];
6255
8306
  const detailsUpdate = { ...this._entityDetails.get() };
6256
8307
  data.results.bindings.forEach((b) => {
@@ -6285,7 +8336,11 @@ GROUP BY ?e1Cat ?e2Cat`;
6285
8336
  await staleWhileRevalidate(
6286
8337
  q,
6287
8338
  async () => {
6288
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8339
+ const data = await this._api.sparql(
8340
+ q,
8341
+ this._projectId,
8342
+ this._graphType
8343
+ );
6289
8344
  const entities = [];
6290
8345
  const relations = [];
6291
8346
  data.results.bindings.forEach((b) => {
@@ -6342,7 +8397,11 @@ SELECT * WHERE {
6342
8397
  ?s geo:hasGeometry/geo:asWKT ?wkt
6343
8398
  }
6344
8399
  }`;
6345
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8400
+ const data = await this._api.sparql(
8401
+ q,
8402
+ this._projectId,
8403
+ this._graphType
8404
+ );
6346
8405
  const update = { ...this._osmWKTMap.get() };
6347
8406
  data.results.bindings.forEach((b) => {
6348
8407
  if (!b["s"] || !b["wkt"])
@@ -6362,13 +8421,14 @@ var CueProjectDocuments = class {
6362
8421
  this._queryCache = _queryCache;
6363
8422
  this._graphType = _graphType;
6364
8423
  this.baseURL = `${rdfBase}${_projectId}/`;
6365
- this._language = language;
8424
+ this._currentLang = language ?? this._api.language;
6366
8425
  this.documentInfoMap = this._documentInfoMap.asReadonly();
6367
8426
  this.projectDocumentsData = this._projectDocumentsData.asReadonly();
6368
8427
  }
6369
8428
  /** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
6370
8429
  baseURL;
6371
- _language;
8430
+ /** Tracks the language for which `_documentInfoMap` is currently populated. */
8431
+ _currentLang;
6372
8432
  _documentInfoMap = new CueSignal({});
6373
8433
  _projectDocumentsData = new CueSignal({
6374
8434
  duplicateCount: 0,
@@ -6398,9 +8458,10 @@ var CueProjectDocuments = class {
6398
8458
  * `requestDocumentData()` call.
6399
8459
  */
6400
8460
  setLanguage(lang) {
6401
- if (this._language === lang)
8461
+ if (this._currentLang === lang)
6402
8462
  return;
6403
- this._language = lang;
8463
+ this._currentLang = lang;
8464
+ this._api.setLanguage(lang);
6404
8465
  this._documentInfoMap.set({});
6405
8466
  }
6406
8467
  // ── Public API ─────────────────────────────────────────────────────────────
@@ -6442,7 +8503,7 @@ var CueProjectDocuments = class {
6442
8503
  if (newUUIDs.length === 0)
6443
8504
  return;
6444
8505
  const values = newUUIDs.map((id) => `r:${id}`).join(" ");
6445
- const lang = this._language;
8506
+ const lang = this._api.language;
6446
8507
  const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
6447
8508
  PREFIX r: <${this.baseURL}>
6448
8509
  SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
@@ -6495,6 +8556,21 @@ GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
6495
8556
  (err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
6496
8557
  );
6497
8558
  }
8559
+ /**
8560
+ * Returns a single arbitrary file path from the project's triplestore.
8561
+ * Useful for pre-filling path-based query inputs with a realistic example.
8562
+ */
8563
+ async randomFilePath() {
8564
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8565
+ SELECT ?path
8566
+ WHERE {
8567
+ ?fl a qcy:FileLocation ;
8568
+ qcy:filePath ?path .
8569
+ }
8570
+ LIMIT 1`;
8571
+ const data = await this._api.sparql(q, this._projectId, this._graphType);
8572
+ return data.results.bindings[0]?.["path"]?.value ?? null;
8573
+ }
6498
8574
  // ── Private helpers ────────────────────────────────────────────────────────
6499
8575
  async _fetchDocumentsBySuffix() {
6500
8576
  return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
@@ -6597,6 +8673,7 @@ var CueProjectView = class {
6597
8673
  this.availableContentCategories = this.schema.availableContentCategories;
6598
8674
  this.availableEntityCategories = this.schema.availableEntityCategories;
6599
8675
  this.availableEntityRelationships = this.schema.availableEntityRelationships;
8676
+ this.schemaReady = this.schema.ready;
6600
8677
  this.entityInfoMap = this.entities.entityInfoMap;
6601
8678
  this.entityGraph = this.entities.entityGraph;
6602
8679
  this.documentInfoMap = this.documents.documentInfoMap;
@@ -6617,6 +8694,11 @@ var CueProjectView = class {
6617
8694
  availableEntityCategories;
6618
8695
  /** Available entity relationship types. Auto-fetched on init. */
6619
8696
  availableEntityRelationships;
8697
+ /**
8698
+ * Resolves when the initial schema load has completed. Await before reading
8699
+ * schema signal values imperatively.
8700
+ */
8701
+ schemaReady;
6620
8702
  /** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
6621
8703
  entityInfoMap;
6622
8704
  /** Project-level entity co-occurrence graph. Fetched once on init. */
@@ -7724,7 +9806,10 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
7724
9806
 
7725
9807
  // libs/js/cue-sdk/src/lib/sync.ts
7726
9808
  async function _fs2() {
7727
- return import("fs/promises");
9809
+ return import(
9810
+ /* webpackIgnore: true */
9811
+ "fs/promises"
9812
+ );
7728
9813
  }
7729
9814
  async function _readNodeFile(fullPath) {
7730
9815
  if (typeof window !== "undefined") {
@@ -7759,11 +9844,17 @@ async function _initWasm() {
7759
9844
  _scanFn = mod.scan;
7760
9845
  } else {
7761
9846
  const { readFile } = await _fs2();
7762
- const { join: join4 } = await import("path");
7763
- const { pathToFileURL } = await import("url");
9847
+ const { join: join4 } = await import(
9848
+ /* webpackIgnore: true */
9849
+ "path"
9850
+ );
9851
+ const { pathToFileURL } = await import(
9852
+ /* webpackIgnore: true */
9853
+ "url"
9854
+ );
7764
9855
  const wasmDir = join4(__dirname, "assets", "wasm");
7765
9856
  const wasmBinary = await readFile(join4(wasmDir, "dir_scanner_wasm_bg.wasm"));
7766
- const glueUrl = pathToFileURL(join4(wasmDir, "dir_scanner_wasm.mjs")).href;
9857
+ const glueUrl = pathToFileURL(join4(wasmDir, "dir_scanner_wasm.js")).href;
7767
9858
  const mod = await import(
7768
9859
  /* @vite-ignore */
7769
9860
  glueUrl
@@ -7772,12 +9863,18 @@ async function _initWasm() {
7772
9863
  _scanFn = mod.scan;
7773
9864
  }
7774
9865
  }
7775
- var DEFAULT_GRAPH_TYPE = "fuseki";
9866
+ var DEFAULT_GRAPH_TYPE = "qlever";
7776
9867
  var FSS_BATCH_CHUNK_SIZE = 1e3;
7777
9868
  var PENDING_LS_PREFIX = "cue:pending:";
7778
9869
  async function _pendingFilePath(spaceId) {
7779
- const { tmpdir } = await import("os");
7780
- const { join: join4 } = await import("path");
9870
+ const { tmpdir } = await import(
9871
+ /* webpackIgnore: true */
9872
+ "os"
9873
+ );
9874
+ const { join: join4 } = await import(
9875
+ /* webpackIgnore: true */
9876
+ "path"
9877
+ );
7781
9878
  return join4(tmpdir(), `cue-sync-pending-${spaceId}.json`);
7782
9879
  }
7783
9880
  async function _loadPending(spaceId) {
@@ -8412,6 +10509,27 @@ var Cue = class _Cue {
8412
10509
  _app;
8413
10510
  _endpoints;
8414
10511
  _isEmulator;
10512
+ _gis = null;
10513
+ /**
10514
+ * Reactive GIS service. Lazily constructed on first access.
10515
+ *
10516
+ * @example
10517
+ * ```ts
10518
+ * cue.gis.setProjectId('my-project');
10519
+ * cue.gis.onAvailableCategories(cats => ...);
10520
+ * cue.gis.setBbox([west, south, east, north]);
10521
+ * cue.gis.setSelectedCategories(new Set(['cadastre', 'building']));
10522
+ * ```
10523
+ */
10524
+ get gis() {
10525
+ if (!this._gis) {
10526
+ this._gis = new CueGis(
10527
+ () => this.api.getAuthHeaders(),
10528
+ this._endpoints.gatewayUrl
10529
+ );
10530
+ }
10531
+ return this._gis;
10532
+ }
8415
10533
  constructor(config = {}) {
8416
10534
  const usingDefaults = !config.apiKey && !config.appId && !config.measurementId;
8417
10535
  if (usingDefaults) {
@@ -9398,10 +11516,17 @@ async function syncHandler(options) {
9398
11516
  process.exit(0);
9399
11517
  } catch (err) {
9400
11518
  const msg = err instanceof Error ? err.message : String(err);
11519
+ const cause = err instanceof Error ? err.cause : void 0;
11520
+ const causeCode = cause instanceof Error ? cause.code : void 0;
11521
+ const causeMsg = cause instanceof Error ? cause.message : void 0;
11522
+ const isTimeout = causeCode === "UND_ERR_HEADERS_TIMEOUT" || causeCode === "UND_ERR_BODY_TIMEOUT" || causeCode === "UND_ERR_CONNECT_TIMEOUT" || causeMsg?.includes("Timeout") || msg.includes("fetch failed");
9401
11523
  if (msg.includes("GRAPH_UNAVAILABLE")) {
9402
11524
  console.error("The knowledge graph for this project has no database configured. Contact your administrator or check the project setup.");
9403
- } else if (msg.includes("GRAPH_TIMEOUT")) {
9404
- console.error("Could not reach the knowledge graph. Make sure all required services are running.");
11525
+ } else if (msg.includes("GRAPH_TIMEOUT") || isTimeout) {
11526
+ console.error("Could not reach the QAECY API \u2014 the request timed out or the server could not be reached.");
11527
+ console.error("Check your internet connection and try again. If the problem persists, the service may be temporarily unavailable.");
11528
+ if (verbose && causeCode)
11529
+ console.error(` Cause: ${causeMsg ?? causeCode}`);
9405
11530
  } else if (msg.includes("METADATA_SYNC_FAILED")) {
9406
11531
  console.error("Metadata sync failed. Try again.");
9407
11532
  } else if (msg.includes("LEDGER_WRITE_FAILED") || msg.includes("File structure batch POST failed")) {
@@ -9409,7 +11534,13 @@ async function syncHandler(options) {
9409
11534
  "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."
9410
11535
  );
9411
11536
  } else {
9412
- console.error("[syncHandler] Unexpected error:", err);
11537
+ console.error("An unexpected error occurred during sync.");
11538
+ if (verbose) {
11539
+ console.error("Details:", err);
11540
+ } else {
11541
+ console.error(` ${msg}`);
11542
+ console.error("Re-run with --verbose for more details.");
11543
+ }
9413
11544
  }
9414
11545
  process.exit(1);
9415
11546
  }
@@ -9872,6 +12003,97 @@ Project created \u2705`);
9872
12003
  }
9873
12004
  }
9874
12005
 
12006
+ // apps/desktop/cue-cli/src/cue-cli-app-builder-tools.ts
12007
+ var import_commander = require("commander");
12008
+ async function buildCueClient(options) {
12009
+ const key = options.key ?? process.env["CUE_API_KEY"];
12010
+ if (!key) {
12011
+ console.error("API key is required. Provide it via --key or CUE_API_KEY env variable.");
12012
+ process.exit(1);
12013
+ }
12014
+ const cue = new CueNode({
12015
+ apiKey: FIREBASE_CONFIG().apiKey,
12016
+ appId: FIREBASE_CONFIG().appId,
12017
+ measurementId: FIREBASE_CONFIG().measurementId,
12018
+ environment: options.emulators ? "emulator" : "production",
12019
+ ...options.emulators ? { endpoints: getEmulatorEndpoints() } : {}
12020
+ });
12021
+ await cue.auth.signInWithApiKey(key);
12022
+ return cue;
12023
+ }
12024
+ async function listProjectsHandler(options) {
12025
+ try {
12026
+ const cue = await buildCueClient(options);
12027
+ if (options.verbose)
12028
+ console.error("Fetching projects...");
12029
+ const projects = await cue.api.projects.listProjects();
12030
+ const output = projects.map((p) => ({
12031
+ id: p.id,
12032
+ name: p.name,
12033
+ organizationID: p.organizationID,
12034
+ isPublic: p.isPublic,
12035
+ graphType: p.projectSettings?.graph?.type ?? "qlever",
12036
+ lastSync: p.lastSync
12037
+ }));
12038
+ console.log(JSON.stringify(output, null, 2));
12039
+ } catch (err) {
12040
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12041
+ process.exit(1);
12042
+ }
12043
+ }
12044
+ async function entitySummaryGraphHandler(options) {
12045
+ try {
12046
+ const cue = await buildCueClient(options);
12047
+ if (options.verbose)
12048
+ console.error(`Fetching entity summary graph for project ${options.space}...`);
12049
+ const entities = new CueProjectEntities(cue.api, options.space);
12050
+ if (options.format === "graph") {
12051
+ const graph = await entities.buildSummaryGraph("graph");
12052
+ console.log(JSON.stringify(graph, null, 2));
12053
+ } else if (options.format === "md") {
12054
+ const md = await entities.buildSummaryGraph("md");
12055
+ console.log(md);
12056
+ } else {
12057
+ const raw = await entities.buildSummaryGraph();
12058
+ console.log(JSON.stringify(raw, null, 2));
12059
+ }
12060
+ } catch (err) {
12061
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12062
+ process.exit(1);
12063
+ }
12064
+ }
12065
+ async function sparqlHandler(options) {
12066
+ try {
12067
+ let sparqlQuery = options.query;
12068
+ if (!sparqlQuery) {
12069
+ if (process.stdin.isTTY) {
12070
+ console.error("Provide a SPARQL query via --query or pipe it via stdin.");
12071
+ process.exit(1);
12072
+ }
12073
+ const chunks = [];
12074
+ for await (const chunk of process.stdin)
12075
+ chunks.push(chunk);
12076
+ sparqlQuery = Buffer.concat(chunks).toString("utf8").trim();
12077
+ }
12078
+ if (!sparqlQuery) {
12079
+ console.error("SPARQL query is empty.");
12080
+ process.exit(1);
12081
+ }
12082
+ const cue = await buildCueClient(options);
12083
+ if (options.verbose)
12084
+ console.error(`Executing SPARQL query against project ${options.space}...`);
12085
+ const result = await cue.api.sparql(sparqlQuery, options.space);
12086
+ console.log(JSON.stringify(result, null, 2));
12087
+ } catch (err) {
12088
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12089
+ process.exit(1);
12090
+ }
12091
+ }
12092
+ var appBuilderToolsCommand = new import_commander.Command("app-builder-tools").description("Tools for agent-assisted Cue app development. Outputs JSON for machine consumption.");
12093
+ 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);
12094
+ 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);
12095
+ 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);
12096
+
9875
12097
  // apps/desktop/cue-cli/src/main.ts
9876
12098
  var packageJson;
9877
12099
  try {
@@ -9884,7 +12106,7 @@ try {
9884
12106
  console.warn("Could not find package.json, using fallback version");
9885
12107
  }
9886
12108
  }
9887
- var program = new import_commander.Command();
12109
+ var program = new import_commander2.Command();
9888
12110
  program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
9889
12111
  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);
9890
12112
  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);
@@ -9894,4 +12116,5 @@ program.command("repair-ttl").description("Repair TTL files in the specified spa
9894
12116
  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);
9895
12117
  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);
9896
12118
  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);
12119
+ program.addCommand(appBuilderToolsCommand);
9897
12120
  program.parse(process.argv);