@qaecy/cue-cli 0.0.25 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/main.js +1675 -803
  2. package/package.json +5 -5
package/main.js CHANGED
@@ -94,19 +94,19 @@ var init_worker_pool = __esm({
94
94
  // apps/desktop/cue-cli/src/main.ts
95
95
  var import_commander = require("commander");
96
96
  var import_fs9 = require("fs");
97
- var import_path5 = require("path");
97
+ var import_path6 = require("path");
98
98
 
99
99
  // apps/desktop/cue-cli/src/variables.ts
100
100
  var import_path = require("path");
101
101
  var EMULATOR_API_GATEWAY_PORT = process.env.CUE_EMULATOR_API_GATEWAY_PORT ?? "8093";
102
102
  var EMULATOR_API_GATEWAY_BASE = `http://localhost:${EMULATOR_API_GATEWAY_PORT}`;
103
103
  var TOKEN_ENDPOINT_EMULATOR = `${EMULATOR_API_GATEWAY_BASE}/token`;
104
- var TOKEN_ENDPOINT = "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token";
104
+ var EMULATOR_QLEVER_PORT = process.env.CUE_QLEVER_PORT ?? "8102";
105
+ var QLEVER_QUERY_ENDPOINT_EMULATOR = process.env.CUE_QLEVER_ENDPOINT ? `${process.env.CUE_QLEVER_ENDPOINT}/query` : `http://localhost:${EMULATOR_QLEVER_PORT}/query`;
106
+ var QLEVER_UPDATE_ENDPOINT_EMULATOR = process.env.CUE_QLEVER_ENDPOINT ? `${process.env.CUE_QLEVER_ENDPOINT}/update` : `http://localhost:${EMULATOR_QLEVER_PORT}/update`;
105
107
  var SPARQL_ENDPOINT_EMULATOR = `${EMULATOR_API_GATEWAY_BASE}/triplestore/query`;
106
108
  var SPARQL_ENDPOINT = "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/triplestore/query";
107
109
  var HASH_WORKER_PATH = (0, import_path.join)(__dirname, "hash-worker.js");
108
- var SERVICE_ID = "cue-cli";
109
- var RDF_BASE = "https://cue.qaecy.com/r/";
110
110
  var PROJECT_ID = "qaecy-mvp-406413.appspot.com";
111
111
  var FIREBASE_CONFIG = (useEmulator = false) => ({
112
112
  apiKey: "AIzaSyCLhz5Wa3ZCERQZVurSt9bqupPeREALFLk",
@@ -418,16 +418,36 @@ var CueFirebase = class _CueFirebase {
418
418
  throw new Error("Storage persistence is not initialized");
419
419
  if (this._app === void 0)
420
420
  throw new Error("App is not initialized");
421
+ const _env = typeof process !== "undefined" ? process.env : {};
422
+ const authEmulatorEnv = _env["FIREBASE_AUTH_EMULATOR_HOST"];
423
+ const authEmulatorUrl = authEmulatorEnv ? `http://${authEmulatorEnv}` : "http://localhost:9099";
424
+ const firestoreEnv = _env["FIRESTORE_EMULATOR_HOST"];
425
+ const [firestoreHost, firestorePortStr] = firestoreEnv ? firestoreEnv.split(":") : ["localhost", "8080"];
426
+ const firestorePort = parseInt(firestorePortStr ?? "8080", 10);
427
+ const storageEnv = _env["STORAGE_EMULATOR_HOST"];
428
+ let storageHost = "localhost";
429
+ let storagePort = 9199;
430
+ if (storageEnv) {
431
+ try {
432
+ const u = new URL(storageEnv.startsWith("http") ? storageEnv : `http://${storageEnv}`);
433
+ storageHost = u.hostname;
434
+ storagePort = parseInt(u.port || "9199", 10);
435
+ } catch {
436
+ const parts = storageEnv.split(":");
437
+ storageHost = parts[0];
438
+ storagePort = parseInt(parts[1] ?? "9199", 10);
439
+ }
440
+ }
421
441
  const functions = (0, import_functions.getFunctions)(this._app, GCP_REGION);
422
- (0, import_auth.connectAuthEmulator)(this._auth, "http://localhost:9099");
423
- (0, import_firestore.connectFirestoreEmulator)((0, import_firestore.getFirestore)(this._app), "localhost", 8080);
442
+ (0, import_auth.connectAuthEmulator)(this._auth, authEmulatorUrl);
443
+ (0, import_firestore.connectFirestoreEmulator)((0, import_firestore.getFirestore)(this._app), firestoreHost, firestorePort);
424
444
  (0, import_functions.connectFunctionsEmulator)(functions, "localhost", 5001);
425
- (0, import_storage.connectStorageEmulator)(this._storageProcessed, "localhost", 9199);
426
- (0, import_storage.connectStorageEmulator)(this._storageRaw, "localhost", 9199);
427
- (0, import_storage.connectStorageEmulator)(this._storageChatSessions, "localhost", 9199);
428
- (0, import_storage.connectStorageEmulator)(this._storageLogs, "localhost", 9199);
429
- (0, import_storage.connectStorageEmulator)(this._storagePublic, "localhost", 9199);
430
- (0, import_storage.connectStorageEmulator)(this._storagePersistence, "localhost", 9199);
445
+ (0, import_storage.connectStorageEmulator)(this._storageProcessed, storageHost, storagePort);
446
+ (0, import_storage.connectStorageEmulator)(this._storageRaw, storageHost, storagePort);
447
+ (0, import_storage.connectStorageEmulator)(this._storageChatSessions, storageHost, storagePort);
448
+ (0, import_storage.connectStorageEmulator)(this._storageLogs, storageHost, storagePort);
449
+ (0, import_storage.connectStorageEmulator)(this._storagePublic, storageHost, storagePort);
450
+ (0, import_storage.connectStorageEmulator)(this._storagePersistence, storageHost, storagePort);
431
451
  if (!this._muted)
432
452
  console.info("Firebase emulators attached");
433
453
  }
@@ -4027,7 +4047,7 @@ async function deleteUnzipped(dir) {
4027
4047
 
4028
4048
  // apps/desktop/cue-cli/src/helpers/query-handler.ts
4029
4049
  var import_auth2 = require("firebase/auth");
4030
- async function queryHandler(query, spaceId, useEmulator) {
4050
+ async function queryHandler(query3, spaceId, useEmulator) {
4031
4051
  const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
4032
4052
  const token = await (0, import_auth2.getAuth)().currentUser?.getIdToken();
4033
4053
  const res = await fetch(endpoint, {
@@ -4038,7 +4058,7 @@ async function queryHandler(query, spaceId, useEmulator) {
4038
4058
  "Content-Type": "application/sparql-query",
4039
4059
  Accept: "application/sparql-results+json"
4040
4060
  },
4041
- body: query
4061
+ body: query3
4042
4062
  });
4043
4063
  if (!res.ok) {
4044
4064
  console.error(
@@ -4058,639 +4078,705 @@ function fileSizePretty(size) {
4058
4078
  return (size / Math.pow(1024, i)).toFixed(2) + " " + sizes[i];
4059
4079
  }
4060
4080
 
4061
- // apps/desktop/cue-cli/src/helpers/fetch-custom-token.ts
4062
- async function fetchCustomToken(pid, apiKey, useEmulator) {
4063
- const url = useEmulator ? TOKEN_ENDPOINT_EMULATOR : TOKEN_ENDPOINT;
4064
- const data = await fetch(url, {
4065
- method: "GET",
4066
- headers: {
4067
- "x-project-id": pid,
4068
- "cue-api-key": apiKey
4069
- }
4070
- });
4071
- if (!data.ok) {
4072
- console.error("Failed to fetch token:", data.statusText);
4073
- process.exit(1);
4074
- }
4075
- const result = await data.json();
4076
- return result.token;
4077
- }
4081
+ // libs/js/cue-sdk/src/lib/cue.ts
4082
+ var import_app2 = require("firebase/app");
4078
4083
 
4079
- // apps/desktop/cue-cli/src/helpers/auth.ts
4084
+ // libs/js/cue-sdk/src/lib/auth.ts
4080
4085
  var import_auth3 = require("firebase/auth");
4081
- async function authenticate(emulators, key, verbose = false) {
4082
- if (!key) {
4083
- key = process.env.CUE_API_KEY;
4084
- if (!key) {
4085
- console.error(
4086
- "API key is required. Provide it via --key option or CUE_API_KEY environment variable."
4086
+ var MICROSOFT_PROVIDER_ID = "microsoft.com";
4087
+ var CueAuth = class {
4088
+ _auth;
4089
+ _endpoints;
4090
+ constructor(app, useEmulator = false, endpoints) {
4091
+ this._auth = (0, import_auth3.getAuth)(app);
4092
+ this._endpoints = endpoints;
4093
+ if (useEmulator) {
4094
+ (0, import_auth3.connectAuthEmulator)(this._auth, endpoints.authEmulatorUrl, {
4095
+ disableWarnings: true
4096
+ });
4097
+ }
4098
+ }
4099
+ async signIn(provider, credentials) {
4100
+ if (provider === "password") {
4101
+ if (!credentials)
4102
+ throw new Error("credentials are required for password sign-in");
4103
+ const result2 = await (0, import_auth3.signInWithEmailAndPassword)(
4104
+ this._auth,
4105
+ credentials.email,
4106
+ credentials.password
4087
4107
  );
4088
- process.exit(1);
4108
+ return result2.user;
4089
4109
  }
4110
+ const firebaseProvider = provider === "google" ? new import_auth3.GoogleAuthProvider() : new import_auth3.OAuthProvider(MICROSOFT_PROVIDER_ID);
4111
+ const result = await (0, import_auth3.signInWithPopup)(this._auth, firebaseProvider);
4112
+ return result.user;
4090
4113
  }
4091
- const fb = CueFirebase.getInstance(
4092
- FIREBASE_CONFIG(emulators),
4093
- void 0,
4094
- !verbose
4095
- );
4096
- if (verbose)
4097
- console.info("Fetching custom token \u23F3");
4098
- const customToken = await fetchCustomToken("xx", key, emulators);
4099
- if (verbose)
4100
- console.info("Fetched token \u2705");
4101
- if (verbose)
4102
- console.info("Signing in \u23F3");
4103
- const userCredentials = await (0, import_auth3.signInWithCustomToken)(fb.auth, customToken);
4104
- if (verbose)
4105
- console.info("Signed in \u2705");
4106
- const idTokenResult = await userCredentials.user.getIdTokenResult();
4107
- const role = idTokenResult.claims["role"];
4108
- const isSuperAdmin = role === "superadmin";
4109
- return { isSuperAdmin, userId: userCredentials.user.uid };
4110
- }
4114
+ /** Sign in with a Cue API key */
4115
+ async signInWithApiKey(cueApiKey, projectId) {
4116
+ const response = await fetch(this._endpoints.tokenUrl, {
4117
+ method: "GET",
4118
+ headers: {
4119
+ "x-project-id": projectId,
4120
+ "cue-api-key": cueApiKey
4121
+ }
4122
+ });
4123
+ if (!response.ok)
4124
+ throw new Error(`Failed to fetch custom token: ${response.statusText}`);
4125
+ const { token } = await response.json();
4126
+ const result = await (0, import_auth3.signInWithCustomToken)(this._auth, token);
4127
+ return result.user;
4128
+ }
4129
+ /** Sign out the current user */
4130
+ async signOut() {
4131
+ await (0, import_auth3.signOut)(this._auth);
4132
+ }
4133
+ /** Currently signed-in user, or null if not authenticated */
4134
+ get currentUser() {
4135
+ return this._auth.currentUser;
4136
+ }
4137
+ /** Subscribe to authentication state changes. Returns an unsubscribe function. */
4138
+ onAuthStateChanged(listener) {
4139
+ return (0, import_auth3.onAuthStateChanged)(this._auth, listener);
4140
+ }
4141
+ /** Get the Firebase ID token for the current user, or null if not authenticated */
4142
+ async getToken(forceRefresh = false) {
4143
+ const user = this._auth.currentUser;
4144
+ if (!user)
4145
+ return null;
4146
+ return user.getIdToken(forceRefresh);
4147
+ }
4148
+ /** Raw Firebase Auth instance, for advanced use cases */
4149
+ get firebaseAuth() {
4150
+ return this._auth;
4151
+ }
4152
+ };
4111
4153
 
4112
- // apps/desktop/cue-cli/src/cue-cli-compare.ts
4113
- async function compareHandler(options) {
4114
- const { space, path, verbose, provider, emulators, zip } = options;
4115
- try {
4116
- await authenticate(emulators, options.key, verbose);
4117
- if (verbose)
4118
- console.info("Building compare base \u23F3");
4119
- const qh = async (query) => queryHandler(query, space, emulators);
4120
- const [localFiles, remoteFiles] = await Promise.all([
4121
- listLocalFiles(
4122
- path,
4123
- provider,
4124
- verbose,
4125
- 5,
4126
- IGNORED_LOCAL,
4127
- HASH_WORKER_PATH,
4128
- zip
4129
- ),
4130
- listRemoteFiles(space, provider, qh, verbose)
4131
- ]);
4132
- const unzipPromise = zip ? deleteUnzipped(path) : Promise.resolve();
4133
- if (zip) {
4134
- if (verbose)
4135
- console.info("Started deletion of temp unzipped dirs \u23F3");
4154
+ // libs/js/cue-sdk/src/lib/api.ts
4155
+ var ENDPOINT_SEARCH = "/assistant/search";
4156
+ var ENDPOINT_SPARQL = "/triplestore/query";
4157
+ var CueApi = class {
4158
+ constructor(_auth, _gatewayUrl, projects, sync) {
4159
+ this._auth = _auth;
4160
+ this._gatewayUrl = _gatewayUrl;
4161
+ this.projects = projects;
4162
+ this.sync = sync;
4163
+ }
4164
+ async _authHeaders() {
4165
+ return this.getAuthHeaders();
4166
+ }
4167
+ /**
4168
+ * Returns standard authentication headers for the current user.
4169
+ * Useful when calling Cue-backed services directly (e.g. the GIS proxy).
4170
+ */
4171
+ async getAuthHeaders() {
4172
+ const token = await this._auth.getToken();
4173
+ if (!token)
4174
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
4175
+ return {
4176
+ Authorization: `Bearer ${token}`,
4177
+ "Content-Type": "application/json"
4178
+ };
4179
+ }
4180
+ /**
4181
+ * Search project documents using natural language.
4182
+ * The user must be authenticated before calling this.
4183
+ */
4184
+ async search(request) {
4185
+ const headers = await this._authHeaders();
4186
+ const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SEARCH}`, {
4187
+ method: "POST",
4188
+ headers,
4189
+ body: JSON.stringify({
4190
+ term: request.term,
4191
+ projectId: request.projectId,
4192
+ categories: request.categories ?? []
4193
+ })
4194
+ });
4195
+ if (!response.ok) {
4196
+ throw new Error(`Search request failed: ${response.status} ${response.statusText}`);
4136
4197
  }
4137
- const report = await compareLocalRemote(localFiles, remoteFiles);
4138
- await unzipPromise;
4139
- if (zip && verbose)
4140
- console.info("Cleaned up unzipped files \u2705");
4141
- if (verbose)
4142
- console.info("Built compare base \u2705");
4143
- console.log("");
4144
- console.log("--- Compare Report ---");
4145
- console.log("");
4146
- console.log(`Total files: ${report.totalCount}`);
4147
- console.log(`Total size: ${fileSizePretty(report.totalSize || 0)}`);
4148
- console.log("");
4149
- console.log(
4150
- `Files synchronized: ${report.syncCount} (${(report.synctPctCount * 100).toFixed(2)}%)`
4151
- );
4152
- console.log(
4153
- `Synchronized size: ${fileSizePretty(report.syncSize || 0)} (${(report.synctPctSize * 100).toFixed(2)}%)`
4154
- );
4155
- console.log("");
4156
- if (report.localNotOnRemote) {
4157
- console.log(
4158
- `${report.localNotOnRemote.length} files do not exist on remote`
4159
- );
4160
- if (verbose && report.localNotOnRemote.length > 0) {
4161
- for (const f of report.localNotOnRemote) {
4162
- console.log(
4163
- " - " + f.relativePath + " (" + fileSizePretty(f.size || 0) + ")"
4164
- );
4165
- }
4166
- }
4167
- console.log("");
4198
+ return response.json();
4199
+ }
4200
+ /**
4201
+ * Execute a SPARQL query against the project's triplestore.
4202
+ * The user must be authenticated before calling this.
4203
+ */
4204
+ async sparql(query3, projectId) {
4205
+ const headers = await this._authHeaders();
4206
+ const response = await fetch(`${this._gatewayUrl}${ENDPOINT_SPARQL}`, {
4207
+ method: "POST",
4208
+ headers,
4209
+ body: JSON.stringify({ query: query3, projectId })
4210
+ });
4211
+ if (!response.ok) {
4212
+ throw new Error(`SPARQL query failed: ${response.status} ${response.statusText}`);
4168
4213
  }
4169
- if (report.localNotOnRemotePathOnly) {
4170
- console.log(
4171
- `${report.localNotOnRemotePathOnly.length} file paths do not exist on remote on providerId "${provider}" (file duplicates)`
4172
- );
4173
- if (verbose && report.localNotOnRemotePathOnly.length > 0) {
4174
- for (const f of report.localNotOnRemotePathOnly) {
4175
- console.log(
4176
- " - " + f.relativePath + " (" + fileSizePretty(f.size || 0) + ")"
4177
- );
4178
- }
4179
- }
4180
- console.log("");
4214
+ return response.json();
4215
+ }
4216
+ };
4217
+
4218
+ // libs/js/cue-sdk/src/lib/project.ts
4219
+ var import_firestore2 = require("firebase/firestore");
4220
+ var COLLECTION_PROJECTS2 = "projects";
4221
+ var CueProjects = class {
4222
+ constructor(_auth, app, useEmulator = false, endpoints) {
4223
+ this._auth = _auth;
4224
+ this._db = (0, import_firestore2.getFirestore)(app);
4225
+ if (useEmulator) {
4226
+ const host = endpoints?.firestoreEmulatorHost ?? "localhost";
4227
+ const port = endpoints?.firestoreEmulatorPort ?? 8080;
4228
+ (0, import_firestore2.connectFirestoreEmulator)(this._db, host, port);
4181
4229
  }
4182
- if (report.remoteNotOnLocal) {
4183
- console.log(`${report.remoteNotOnLocal.length} files do not exist locally`);
4184
- console.log(
4185
- "This might expected if the files belong to another provider or have been deleted locally"
4186
- );
4187
- if (verbose && report.remoteNotOnLocal.length > 0) {
4188
- for (const f of report.remoteNotOnLocal) {
4189
- console.log(
4190
- " - " + f.contentUUID + " (" + fileSizePretty(f.size || 0) + ")"
4191
- );
4192
- }
4193
- }
4194
- console.log("");
4230
+ }
4231
+ _db;
4232
+ _requireUser() {
4233
+ const user = this._auth.currentUser;
4234
+ if (!user)
4235
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
4236
+ return user.uid;
4237
+ }
4238
+ /**
4239
+ * Create a new project. The authenticated user is automatically set as admin, syncer, and member.
4240
+ * Throws if a project with the given ID already exists.
4241
+ */
4242
+ async createProject(options) {
4243
+ const userId = this._requireUser();
4244
+ const projectId = options.id ?? crypto.randomUUID();
4245
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4246
+ const projectSettings = { views: [], chatDisabled: false };
4247
+ const ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4248
+ const existing = await (0, import_firestore2.getDoc)(ref5);
4249
+ if (existing.exists()) {
4250
+ throw new Error(`Project with ID "${projectId}" already exists.`);
4195
4251
  }
4196
- if (report.remoteNotOnLocalPathOnly) {
4197
- console.log(
4198
- `${report.remoteNotOnLocalPathOnly.length} file paths on providerId "${provider}" do not exist locally`
4199
- );
4200
- console.log(
4201
- "This might expected if the files belong to another provider or have been deleted locally"
4202
- );
4203
- if (verbose && report.remoteNotOnLocalPathOnly.length > 0) {
4204
- for (const f of report.remoteNotOnLocalPathOnly) {
4205
- console.log(
4206
- " - " + f.contentUUID + " (" + fileSizePretty(f.size || 0) + ")"
4207
- );
4252
+ const data = {
4253
+ id: projectId,
4254
+ name: options.name,
4255
+ organizationID: options.organizationID,
4256
+ created: now,
4257
+ lastSync: null,
4258
+ isPublic: false,
4259
+ members: [userId],
4260
+ syncers: [userId],
4261
+ admins: [userId],
4262
+ alternativeIDs: [projectId],
4263
+ projectSettings
4264
+ };
4265
+ await (0, import_firestore2.setDoc)(ref5, data);
4266
+ return data;
4267
+ }
4268
+ /**
4269
+ * List all projects where the authenticated user appears in the members, syncers, or admins array.
4270
+ * Runs three parallel Firestore queries and deduplicates by project ID.
4271
+ */
4272
+ async listProjects() {
4273
+ const userId = this._requireUser();
4274
+ const col = (0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2);
4275
+ const [memberSnap, syncerSnap, adminSnap] = await Promise.all([
4276
+ (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("members", "array-contains", userId))),
4277
+ (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("syncers", "array-contains", userId))),
4278
+ (0, import_firestore2.getDocs)((0, import_firestore2.query)(col, (0, import_firestore2.where)("admins", "array-contains", userId)))
4279
+ ]);
4280
+ const seen = /* @__PURE__ */ new Set();
4281
+ const results = [];
4282
+ for (const snap of [memberSnap, syncerSnap, adminSnap]) {
4283
+ for (const d of snap.docs) {
4284
+ const project = d.data();
4285
+ if (!seen.has(project.id)) {
4286
+ seen.add(project.id);
4287
+ results.push(project);
4208
4288
  }
4209
4289
  }
4210
- console.log("");
4211
4290
  }
4212
- } catch (err) {
4213
- console.error("Error:", err);
4214
- process.exit(1);
4291
+ return results;
4215
4292
  }
4216
- }
4217
-
4218
- // apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
4219
- var import_storage3 = require("firebase/storage");
4220
- var import_path3 = require("path");
4221
- var import_promises3 = require("fs/promises");
4222
- async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
4223
- const firebase = CueFirebase.getInstance();
4224
- const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
4225
- console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
4226
- const listResult = await (0, import_storage3.listAll)((0, import_storage3.ref)(storage, subDir));
4227
- const outputDir = (0, import_path3.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
4228
- await (0, import_promises3.mkdir)(outputDir, { recursive: true });
4229
- for (const fileRef of listResult.items) {
4230
- console.log(fileRef.name);
4231
- if (subDir && !fileRef.name.includes(subString))
4232
- continue;
4233
- const bytes = await (0, import_storage3.getBytes)(fileRef);
4234
- const outputPath = (0, import_path3.join)(outputDir, fileRef.name);
4235
- await (0, import_promises3.writeFile)(outputPath, Buffer.from(bytes));
4236
- console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
4293
+ /** Fetch a single project by ID. Returns null if not found. */
4294
+ async getProject(projectId) {
4295
+ const ref5 = (0, import_firestore2.doc)((0, import_firestore2.collection)(this._db, COLLECTION_PROJECTS2), projectId);
4296
+ const snap = await (0, import_firestore2.getDoc)(ref5);
4297
+ if (!snap.exists())
4298
+ return null;
4299
+ return snap.data();
4237
4300
  }
4238
- }
4301
+ /**
4302
+ * Atomically increments `unitsConsumed` on the top-level `clientSync/{projectId}`
4303
+ * document, creating it if it doesn't exist. Intended for pre-flight checks.
4304
+ */
4305
+ async incrementUnitsConsumed(projectId, units, userId) {
4306
+ if (units <= 0)
4307
+ return;
4308
+ const ref5 = (0, import_firestore2.doc)(this._db, "clientSync", projectId);
4309
+ await (0, import_firestore2.setDoc)(ref5, {
4310
+ unitsConsumed: (0, import_firestore2.increment)(units),
4311
+ lastUpdated: (0, import_firestore2.serverTimestamp)(),
4312
+ lastUserId: userId
4313
+ }, { merge: true });
4314
+ }
4315
+ };
4239
4316
 
4240
- // apps/desktop/cue-cli/src/cue-cli-dump-processed.ts
4241
- async function dumpProcessedHandler(options) {
4242
- const { space, verbose, emulators, processor } = options;
4243
- try {
4244
- const { isSuperAdmin } = await authenticate(emulators, options.key, verbose);
4245
- if (!isSuperAdmin) {
4246
- console.error("Sorry - this tool is only for super admin users");
4247
- process.exit(1);
4317
+ // libs/js/cue-sdk/src/lib/profile.ts
4318
+ var import_auth4 = require("firebase/auth");
4319
+ var import_firestore3 = require("firebase/firestore");
4320
+ var import_nanoid = require("nanoid");
4321
+ var COLLECTION_API_KEYS2 = "apiKeys";
4322
+ var CueProfile = class {
4323
+ constructor(_auth, app, useEmulator, emulatorHost, emulatorPort) {
4324
+ this._auth = _auth;
4325
+ this._db = (0, import_firestore3.getFirestore)(app);
4326
+ if (useEmulator) {
4327
+ (0, import_firestore3.connectFirestoreEmulator)(this._db, emulatorHost, emulatorPort);
4248
4328
  }
4249
- if (verbose)
4250
- console.info(`Dumping processed files for processor ${processor} \u23F3`);
4251
- const files = await getFilesContainingSubstring("processed", `${space}/triples`, processor);
4252
- console.log(files);
4253
- } catch (err) {
4254
- console.error("Error:", err);
4255
- process.exit(1);
4256
4329
  }
4257
- }
4258
-
4259
- // apps/desktop/cue-cli/src/helpers/graph-dump.ts
4260
- var import_fs2 = require("fs");
4261
- var import_stream = require("stream");
4262
- var import_fs3 = require("fs");
4263
- var import_zlib = require("zlib");
4264
- var import_promises4 = require("stream/promises");
4265
- var import_promises5 = require("fs/promises");
4266
- var import_auth6 = require("firebase/auth");
4267
-
4268
- // libs/js/size-tools/src/lib/js-size-tools.ts
4269
- function humanFileSize(bytes, si = false, dp = 1) {
4270
- const thresh = si ? 1e3 : 1024;
4271
- if (Math.abs(bytes) < thresh) {
4272
- return bytes + " B";
4330
+ _db;
4331
+ _apiKeyDocRef;
4332
+ /** Whether the current user has an active API key. */
4333
+ async hasAPIKey() {
4334
+ const uid = this._auth.currentUser?.uid;
4335
+ if (!uid)
4336
+ return false;
4337
+ return this._checkIfUserHasAPIKey(uid);
4273
4338
  }
4274
- const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
4275
- let u = -1;
4276
- const r = 10 ** dp;
4277
- do {
4278
- bytes /= thresh;
4279
- ++u;
4280
- } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
4281
- return bytes.toFixed(dp) + " " + units[u];
4282
- }
4283
-
4284
- // apps/desktop/cue-cli/src/helpers/graph-dump.ts
4285
- var import_fs4 = require("fs");
4286
- async function dumpRdfGraphToFileJelly(spaceId, useEmulator = false, verbose = false) {
4287
- return dumpRdfGraphToFile(
4288
- spaceId,
4289
- useEmulator,
4290
- verbose,
4291
- `${spaceId}.jelly`,
4292
- "application/x-jelly-rdf"
4293
- );
4294
- }
4295
- async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false, outFile, mimeType = "application/n-quads") {
4296
- const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
4297
- const dataUrl = endpoint.replace("/query", "/data");
4298
- if (verbose)
4299
- console.info(`Streaming RDF graph \u23F3`);
4300
- const filePath = outFile || `${spaceId}.nq`;
4301
- if ((0, import_fs4.existsSync)(filePath) || (0, import_fs4.existsSync)(`${filePath}.gz`)) {
4302
- if (verbose)
4303
- console.info(`File ${filePath} already exists, skipping download.`);
4304
- return mimeType === "application/n-quads" ? `${filePath}.gz` : filePath;
4339
+ /** Returns the sign-in methods registered for the current user's email. */
4340
+ async getSignInMethods() {
4341
+ const user = this._auth.currentUser;
4342
+ if (!user?.email)
4343
+ throw new Error("User has no e-mail");
4344
+ return (0, import_auth4.fetchSignInMethodsForEmail)(this._auth.firebaseAuth, user.email);
4305
4345
  }
4306
- const token = await (0, import_auth6.getAuth)().currentUser?.getIdToken();
4307
- const res = await fetch(dataUrl, {
4308
- method: "GET",
4309
- headers: {
4310
- "x-project-id": spaceId,
4311
- authorization: `Bearer ${token}`,
4312
- Accept: mimeType
4313
- }
4314
- });
4315
- if (!res.ok || !res.body) {
4316
- throw new Error(`Failed to stream data: ${res.status} ${res.statusText}`);
4346
+ /** Builds a human-readable label from a Firebase UserInfo provider entry. */
4347
+ buildProviderLabel(userInfo) {
4348
+ if (userInfo.displayName && userInfo.email)
4349
+ return `${userInfo.displayName} (${userInfo.email})`;
4350
+ return userInfo.displayName ?? userInfo.email ?? "-";
4317
4351
  }
4318
- const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
4319
- const nodeStream = import_stream.Readable.fromWeb(res.body);
4320
- let chunkCount = 0;
4321
- let totalSize = 0;
4322
- let hasDataTimeout = null;
4323
- let lastChunkTime = Date.now();
4324
- nodeStream.on("data", (chunk) => {
4325
- lastChunkTime = Date.now();
4326
- chunkCount++;
4327
- totalSize += chunk.length;
4328
- if (hasDataTimeout) {
4329
- clearTimeout(hasDataTimeout);
4352
+ /** Returns SSO accounts linked to the current user (excludes password). */
4353
+ getSSOAccounts() {
4354
+ const user = this._auth.currentUser;
4355
+ if (!user)
4356
+ return [];
4357
+ return user.providerData.filter((p) => p.providerId !== "password").map((p) => ({ id: p.providerId, label: this.buildProviderLabel(p) }));
4358
+ }
4359
+ /** Links a Google or Microsoft provider to the current account via popup. Returns the new provider label. */
4360
+ async linkProvider(ssoProvider) {
4361
+ const user = this._requireUser();
4362
+ const provider = ssoProvider === "google.com" ? new import_auth4.GoogleAuthProvider() : new import_auth4.OAuthProvider(ssoProvider);
4363
+ const credential = await (0, import_auth4.linkWithPopup)(user, provider);
4364
+ const userInfo = credential.user.providerData.find(
4365
+ (pd) => pd.providerId === ssoProvider
4366
+ );
4367
+ return userInfo ? this.buildProviderLabel(userInfo) : "-";
4368
+ }
4369
+ /** Unlinks a provider from the current account. */
4370
+ async unlinkProvider(providerId) {
4371
+ await (0, import_auth4.unlink)(this._requireUser(), providerId);
4372
+ }
4373
+ /** Changes the password. Reauthenticates first. */
4374
+ async updatePassword(currentPassword, newPassword) {
4375
+ const user = this._requireUser();
4376
+ if (!user.email)
4377
+ throw new Error("User has no e-mail");
4378
+ await (0, import_auth4.reauthenticateWithCredential)(
4379
+ user,
4380
+ import_auth4.EmailAuthProvider.credential(user.email, currentPassword)
4381
+ );
4382
+ await (0, import_auth4.updatePassword)(user, newPassword);
4383
+ }
4384
+ /** Adds (sets) a password for an account that currently only uses SSO. */
4385
+ async addPassword(password) {
4386
+ const user = this._requireUser();
4387
+ if (!user.email)
4388
+ throw new Error("User has no e-mail");
4389
+ await (0, import_auth4.linkWithCredential)(user, import_auth4.EmailAuthProvider.credential(user.email, password));
4390
+ }
4391
+ /** Requests an e-mail change. Sends a verification e-mail to the new address. */
4392
+ async updateEmail(newEmail, password) {
4393
+ const user = this._requireUser();
4394
+ if (!user.email)
4395
+ throw new Error("User e-mail not available");
4396
+ await (0, import_auth4.reauthenticateWithCredential)(
4397
+ user,
4398
+ import_auth4.EmailAuthProvider.credential(user.email, password)
4399
+ );
4400
+ await (0, import_auth4.verifyBeforeUpdateEmail)(user, newEmail);
4401
+ await (0, import_auth4.sendEmailVerification)(user);
4402
+ }
4403
+ /** Creates a new API key for the current user. */
4404
+ async createAPIKey(expiration) {
4405
+ const uid = this._auth.currentUser?.uid;
4406
+ if (!uid)
4407
+ throw new Error("User not authenticated");
4408
+ const data = { key: `cue-${(0, import_nanoid.nanoid)(150)}`, uid, expiration };
4409
+ const col = (0, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4410
+ this._apiKeyDocRef = await (0, import_firestore3.addDoc)(col, data);
4411
+ return data;
4412
+ }
4413
+ /** Revokes the current user's API key. */
4414
+ async revokeAPIKey() {
4415
+ if (!this._apiKeyDocRef)
4416
+ throw new Error("No API key document reference");
4417
+ await (0, import_firestore3.deleteDoc)(this._apiKeyDocRef);
4418
+ this._apiKeyDocRef = void 0;
4419
+ }
4420
+ /** Fetches the current user's existing API key. */
4421
+ async requestAPIKey() {
4422
+ if (!this._apiKeyDocRef)
4423
+ throw new Error("No API key document reference");
4424
+ const doc2 = (await (0, import_firestore3.getDoc)(this._apiKeyDocRef)).data();
4425
+ return { key: doc2.key, expiration: doc2.expiration };
4426
+ }
4427
+ async _checkIfUserHasAPIKey(uid) {
4428
+ const col = (0, import_firestore3.collection)(this._db, COLLECTION_API_KEYS2);
4429
+ const q = (0, import_firestore3.query)(col, (0, import_firestore3.where)("uid", "==", uid), (0, import_firestore3.limit)(1));
4430
+ const snapshot = await (0, import_firestore3.getDocs)(q);
4431
+ if (!snapshot.empty)
4432
+ this._apiKeyDocRef = snapshot.docs[0].ref;
4433
+ return !snapshot.empty;
4434
+ }
4435
+ _requireUser() {
4436
+ const user = this._auth.currentUser;
4437
+ if (!user)
4438
+ throw new Error("Not authenticated");
4439
+ return user;
4440
+ }
4441
+ };
4442
+
4443
+ // libs/js/cue-sdk/src/lib/cue.ts
4444
+ var FIREBASE_PROJECT_ID = "qaecy-mvp-406413";
4445
+ var FIREBASE_SENDER_ID = "734737865998";
4446
+ var ENDPOINTS = {
4447
+ production: {
4448
+ gatewayUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app",
4449
+ tokenUrl: "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/token",
4450
+ authEmulatorUrl: "http://localhost:9099",
4451
+ storageEmulatorHost: "localhost",
4452
+ storageEmulatorPort: 9199,
4453
+ firestoreEmulatorHost: "localhost",
4454
+ firestoreEmulatorPort: 8080
4455
+ },
4456
+ emulator: {
4457
+ gatewayUrl: "http://localhost:8093",
4458
+ tokenUrl: "http://localhost:8093/token",
4459
+ authEmulatorUrl: "http://localhost:9099",
4460
+ storageEmulatorHost: "localhost",
4461
+ storageEmulatorPort: 9199,
4462
+ firestoreEmulatorHost: "localhost",
4463
+ firestoreEmulatorPort: 8080
4464
+ }
4465
+ };
4466
+ var Cue = class {
4467
+ auth;
4468
+ api;
4469
+ profile;
4470
+ _app;
4471
+ _endpoints;
4472
+ _isEmulator;
4473
+ constructor(config) {
4474
+ const env = config.environment ?? "production";
4475
+ this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
4476
+ this._isEmulator = env === "emulator";
4477
+ this._app = (0, import_app2.initializeApp)({
4478
+ apiKey: config.apiKey,
4479
+ appId: config.appId,
4480
+ measurementId: config.measurementId,
4481
+ authDomain: `${FIREBASE_PROJECT_ID}.firebaseapp.com`,
4482
+ projectId: FIREBASE_PROJECT_ID,
4483
+ messagingSenderId: FIREBASE_SENDER_ID
4484
+ });
4485
+ this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
4486
+ const projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
4487
+ this.api = this._buildApi(projects);
4488
+ this.profile = new CueProfile(
4489
+ this.auth,
4490
+ this._app,
4491
+ this._isEmulator,
4492
+ this._endpoints.firestoreEmulatorHost,
4493
+ this._endpoints.firestoreEmulatorPort
4494
+ );
4495
+ }
4496
+ /** Override in subclasses to provide a custom CueApi (e.g. with sync). */
4497
+ _buildApi(projects) {
4498
+ return new CueApi(this.auth, this._endpoints.gatewayUrl, projects);
4499
+ }
4500
+ /** Convenience: get the current user's Firebase ID token */
4501
+ getToken(forceRefresh = false) {
4502
+ return this.auth.getToken(forceRefresh);
4503
+ }
4504
+ };
4505
+
4506
+ // libs/js/cue-sdk/src/lib/cue-node.ts
4507
+ var import_storage4 = require("firebase/storage");
4508
+
4509
+ // libs/js/databases/src/lib/graph/fuseki.ts
4510
+ var Fuseki = class _Fuseki {
4511
+ queryEndpoint;
4512
+ updateEndpoint;
4513
+ baseHeaders;
4514
+ static RELEVANT_HEADER_KEYS = [
4515
+ "authorization",
4516
+ "Authorization",
4517
+ "x-project-id"
4518
+ ];
4519
+ constructor(graphOptions) {
4520
+ this.queryEndpoint = graphOptions.queryEndpoint;
4521
+ this.updateEndpoint = graphOptions.updateEndpoint;
4522
+ this.baseHeaders = Object.fromEntries(
4523
+ Object.entries(graphOptions.originalHeaders || {}).filter(
4524
+ ([key]) => _Fuseki.RELEVANT_HEADER_KEYS.includes(key)
4525
+ )
4526
+ );
4527
+ if (graphOptions.authHeader !== void 0) {
4528
+ this.baseHeaders["Authorization"] = graphOptions.authHeader;
4330
4529
  }
4331
- if (verbose && totalSize >= 10 * 1024 * 1024 && totalSize % (10 * 1024 * 1024) < chunk.length)
4332
- console.info(
4333
- `Writing chunk #${chunkCount}, size: ${humanFileSize(totalSize)}`
4530
+ }
4531
+ async ping() {
4532
+ const query3 = "ASK { }";
4533
+ const res = await this.query(query3);
4534
+ return res.boolean;
4535
+ }
4536
+ async query(query3, accept = "application/sparql-results+json") {
4537
+ let res;
4538
+ try {
4539
+ res = await fetch(this.queryEndpoint, {
4540
+ headers: {
4541
+ ...this.baseHeaders,
4542
+ "Content-Type": "application/x-www-form-urlencoded",
4543
+ Accept: accept
4544
+ },
4545
+ method: "POST",
4546
+ body: new URLSearchParams({ query: query3 })
4547
+ });
4548
+ } catch (err) {
4549
+ throw new Error(
4550
+ `Fuseki is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
4334
4551
  );
4335
- });
4336
- const streamTimeout = 3e5;
4337
- hasDataTimeout = setTimeout(() => {
4338
- const timeSinceLastChunk = Date.now() - lastChunkTime;
4339
- if (timeSinceLastChunk > streamTimeout) {
4340
- console.error(`Stream appears stalled - no data received for ${timeSinceLastChunk}ms`);
4341
- nodeStream.destroy(new Error("Stream timeout"));
4342
4552
  }
4343
- }, streamTimeout);
4344
- try {
4345
- await (0, import_promises4.pipeline)(nodeStream, fileStream);
4346
- } finally {
4347
- if (hasDataTimeout) {
4348
- clearTimeout(hasDataTimeout);
4553
+ if (!res.ok) {
4554
+ const body = await res.text();
4555
+ throw new Error(`Fuseki query failed (HTTP ${res.status}): ${body}`);
4349
4556
  }
4557
+ return await res.json();
4350
4558
  }
4351
- if (verbose)
4352
- console.info(`Total chunks written: ${chunkCount}, total size: ${humanFileSize(totalSize)}`);
4353
- if (totalSize === 0) {
4354
- throw new Error("No data received from server - possible authentication or permission issue");
4559
+ async subset(query3, accept = "text/turtle") {
4560
+ const res = await fetch(this.queryEndpoint, {
4561
+ headers: {
4562
+ ...this.baseHeaders,
4563
+ "Content-Type": "application/x-www-form-urlencoded",
4564
+ Authorization: this.baseHeaders["Authorization"] || "",
4565
+ Accept: accept
4566
+ },
4567
+ method: "POST",
4568
+ body: new URLSearchParams({ query: query3 })
4569
+ });
4570
+ if (accept === "application/ld+json") {
4571
+ return await res.json();
4572
+ }
4573
+ return await res.text();
4355
4574
  }
4356
- if (verbose)
4357
- console.info(`Streamed RDF graph to ${filePath} \u2705`);
4358
- if (mimeType === "application/n-quads") {
4359
- if (verbose)
4360
- console.info(`
4361
- Gzipping to ${filePath}.gz \u23F3`);
4362
- await _doGzip(filePath);
4363
- if (verbose)
4364
- console.info(`Gzipped to ${filePath}.gz \u2705`);
4365
- return `${filePath}.gz`;
4575
+ async update(update) {
4576
+ const res = await fetch(this.updateEndpoint, {
4577
+ headers: {
4578
+ ...this.baseHeaders,
4579
+ "Content-Type": "application/x-www-form-urlencoded"
4580
+ },
4581
+ method: "POST",
4582
+ body: new URLSearchParams({ update })
4583
+ });
4584
+ if (!res.ok) {
4585
+ const body = await res.text();
4586
+ throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
4587
+ }
4588
+ const contentType = res.headers.get("content-type") ?? "";
4589
+ if (contentType.includes("application/json")) {
4590
+ return await res.json();
4591
+ }
4592
+ return {
4593
+ ok: res.ok,
4594
+ status: res.status,
4595
+ message: await res.text()
4596
+ };
4366
4597
  }
4367
- return filePath;
4368
- }
4369
- async function dumpRdfGraphToFileConstruct(spaceId, useEmulator = false, verbose = false, graphIRI, batchSize = 5e4, concurrency = 8, maxRetries = 5, retryDelayMs = 1e3, delayBetweenBatchesMs = 500) {
4370
- const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
4371
- const filePath = `${spaceId}.nq`;
4372
- const graph = graphIRI !== void 0 ? `<${graphIRI}>` : "?g";
4373
- const constructQuery = `
4374
- CONSTRUCT { GRAPH ${graph} { ?s ?p ?o } }
4375
- WHERE { GRAPH ${graph} { ?s ?p ?o } }
4376
- `;
4377
- let offset = 0;
4378
- const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
4379
- let hasMore = true;
4380
- while (hasMore) {
4381
- const queries = Array.from(
4382
- { length: concurrency },
4383
- (_, i) => `${constructQuery} LIMIT ${batchSize} OFFSET ${offset + i * batchSize}`
4384
- );
4385
- offset += batchSize * concurrency;
4386
- const results = await Promise.all(
4387
- queries.map(
4388
- (q, idx) => retryWithBackoff(
4389
- () => _doQuery(q, spaceId, endpoint),
4390
- maxRetries,
4391
- retryDelayMs,
4392
- verbose ? `Batch ${offset - batchSize * concurrency + idx * batchSize}` : void 0
4393
- )
4598
+ };
4599
+
4600
+ // libs/js/databases/src/lib/graph/qlever.ts
4601
+ var QLever = class _QLever {
4602
+ queryEndpoint;
4603
+ updateEndpoint;
4604
+ baseHeaders;
4605
+ static RELEVANT_HEADER_KEYS = [
4606
+ "authorization",
4607
+ "Authorization",
4608
+ "x-project-id",
4609
+ "x-user-roles"
4610
+ // add more if needed
4611
+ ];
4612
+ constructor(graphOptions) {
4613
+ this.queryEndpoint = graphOptions.queryEndpoint;
4614
+ this.updateEndpoint = graphOptions.updateEndpoint;
4615
+ this.baseHeaders = Object.fromEntries(
4616
+ Object.entries(graphOptions.originalHeaders || {}).filter(
4617
+ ([key]) => _QLever.RELEVANT_HEADER_KEYS.includes(key)
4394
4618
  )
4395
4619
  );
4396
- const batches = results.filter(Boolean);
4397
- if (batches.length === 0) {
4398
- hasMore = false;
4399
- break;
4400
- }
4401
- for (const batch of batches) {
4402
- fileStream.write(batch.trim() + "\n");
4403
- }
4404
- if (verbose)
4405
- process.stdout.write(`Current offset: ${offset.toLocaleString()}\r`);
4406
- if (delayBetweenBatchesMs > 0)
4407
- await new Promise((res) => setTimeout(res, delayBetweenBatchesMs));
4408
4620
  }
4409
- fileStream.end();
4410
- if (verbose)
4411
- console.info(`
4412
- Gzipping to ${filePath}.gz \u23F3`);
4413
- await _doGzip(filePath);
4414
- if (verbose)
4415
- console.info(`Gzipped to ${filePath}.gz \u2705`);
4416
- return `${filePath}.gz`;
4417
- }
4418
- async function retryWithBackoff(fn, maxRetries, delayMs, label) {
4419
- let attempt = 0;
4420
- let lastErr = null;
4421
- while (attempt < maxRetries) {
4621
+ async ping() {
4622
+ const query3 = "ASK { }";
4623
+ const res = await this.query(query3);
4624
+ return res.boolean;
4625
+ }
4626
+ async query(query3, accept = "application/sparql-results+json") {
4627
+ let res;
4422
4628
  try {
4423
- const result = await fn();
4424
- if (result !== null && result !== void 0)
4425
- return result;
4629
+ console.log(this.queryEndpoint);
4630
+ res = await fetch(this.queryEndpoint, {
4631
+ headers: {
4632
+ ...this.baseHeaders,
4633
+ "Content-Type": "application/x-www-form-urlencoded",
4634
+ Accept: accept
4635
+ },
4636
+ method: "POST",
4637
+ body: new URLSearchParams({ query: query3 })
4638
+ });
4426
4639
  } catch (err) {
4427
- lastErr = err;
4640
+ throw new Error(
4641
+ `QLever is not accessible at ${this.queryEndpoint}: ${err instanceof Error ? err.message : String(err)}`
4642
+ );
4428
4643
  }
4429
- attempt++;
4430
- if (label)
4431
- console.warn(`${label}: Retry ${attempt}/${maxRetries}`);
4432
- await new Promise(
4433
- (res) => setTimeout(res, delayMs * Math.pow(2, attempt - 1))
4434
- );
4644
+ if (!res.ok) {
4645
+ const body = await res.text();
4646
+ throw new Error(`QLever query failed (HTTP ${res.status}): ${body}`);
4647
+ }
4648
+ return await res.json();
4435
4649
  }
4436
- if (label)
4437
- console.error(`${label}: Failed after ${maxRetries} retries`, lastErr);
4438
- return null;
4439
- }
4440
- async function _doGzip(filePath) {
4441
- const gzFilePath = `${filePath}.gz`;
4442
- await new Promise((resolve2, reject) => {
4443
- const gzip = (0, import_zlib.createGzip)();
4444
- const source = (0, import_fs3.createReadStream)(filePath);
4445
- const dest = (0, import_fs2.createWriteStream)(gzFilePath);
4446
- (0, import_promises4.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
4447
- });
4448
- await (0, import_promises5.unlink)(filePath);
4449
- }
4450
- async function _doQuery(query, spaceId, url) {
4451
- const token = await (0, import_auth6.getAuth)().currentUser?.getIdToken();
4452
- try {
4453
- const res = await fetch(url, {
4650
+ async subset(query3, accept = "text/turtle") {
4651
+ const res = await fetch(this.queryEndpoint, {
4652
+ headers: {
4653
+ ...this.baseHeaders,
4654
+ "Content-Type": "application/x-www-form-urlencoded",
4655
+ Accept: accept
4656
+ },
4454
4657
  method: "POST",
4658
+ body: new URLSearchParams({ query: query3 })
4659
+ });
4660
+ return await res.text();
4661
+ }
4662
+ async update(update) {
4663
+ const res = await fetch(this.updateEndpoint, {
4455
4664
  headers: {
4456
- "x-project-id": spaceId,
4457
- authorization: `Bearer ${token}`,
4458
- "Content-Type": "application/sparql-query",
4459
- Accept: "application/n-quads"
4665
+ ...this.baseHeaders,
4666
+ "Content-Type": "application/x-www-form-urlencoded"
4460
4667
  },
4461
- body: query
4668
+ method: "POST",
4669
+ body: new URLSearchParams({ update })
4462
4670
  });
4463
4671
  if (!res.ok) {
4464
- console.error(`Error: ${res.status} ${res.statusText}`);
4465
- return null;
4672
+ const body = await res.text();
4673
+ throw new Error(`SPARQL update failed (HTTP ${res.status}): ${body}`);
4466
4674
  }
4467
- return await res.text();
4468
- } catch (e) {
4469
- console.error(e);
4470
- return null;
4675
+ return await res.json();
4471
4676
  }
4472
- }
4677
+ };
4473
4678
 
4474
- // apps/desktop/cue-cli/src/helpers/graph-create.ts
4475
- var import_auth7 = require("firebase/auth");
4476
- async function createGraph(id) {
4477
- const token = await (0, import_auth7.getAuth)().currentUser?.getIdToken();
4478
- const dataSetUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", `/$/datasets`);
4479
- const res = await fetch(dataSetUrl, {
4480
- method: "POST",
4481
- headers: {
4482
- "x-project-id": id,
4483
- authorization: `Bearer ${token}`,
4484
- "content-type": "text/turtle"
4485
- },
4486
- body: _getTemplate(id)
4487
- });
4488
- if (!res.ok) {
4489
- throw new Error(`Failed to create graph: ${res.statusText}`);
4679
+ // libs/js/databases/src/lib/graph/main.ts
4680
+ var CueGraphDatabase = class {
4681
+ constructor(options) {
4682
+ this.options = options;
4683
+ switch (this.options.graphType) {
4684
+ case "qlever":
4685
+ this._db = new QLever(this.options);
4686
+ break;
4687
+ case "fuseki":
4688
+ this._db = new Fuseki(this.options);
4689
+ break;
4690
+ default:
4691
+ throw new Error(`Unsupported graph type: ${this.options.graphType}`);
4692
+ }
4490
4693
  }
4491
- }
4492
- function _getTemplate(partition, base = "fuseki-base-new/fuseki") {
4493
- return `@prefix : <http://base/#> .
4494
- @prefix fuseki: <http://jena.apache.org/fuseki#> .
4495
- @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4496
- @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
4497
- @prefix tdb2: <http://jena.apache.org/2016/tdb#> .
4498
- @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
4499
- @prefix text: <http://jena.apache.org/text#> .
4500
- @prefix qcy: <${qaecyPrefixes["qcy"]}> .
4501
- @prefix qcye: <${qaecyPrefixes["qcy-e"]}> .
4694
+ _db;
4695
+ ping() {
4696
+ return this._db.ping();
4697
+ }
4698
+ query(queryString, accept) {
4699
+ return this._db.query(queryString, accept);
4700
+ }
4701
+ subset(queryString, accept) {
4702
+ if (this.options.graphType === "qlever") {
4703
+ if (accept && accept !== "text/turtle") {
4704
+ return Promise.reject(
4705
+ new Error(
4706
+ `QLever only supports 'text/turtle' for CONSTRUCT/DESCRIBE queries.`
4707
+ )
4708
+ );
4709
+ }
4710
+ return this._db.subset(
4711
+ queryString,
4712
+ accept
4713
+ );
4714
+ }
4715
+ return this._db.subset(queryString, accept);
4716
+ }
4717
+ update(updateString) {
4718
+ return this._db.update(updateString);
4719
+ }
4720
+ };
4502
4721
 
4722
+ // libs/js/databases/src/lib/blob/blob.ts
4723
+ var import_storage3 = require("firebase/storage");
4724
+ var CueBlobStorage = class {
4725
+ constructor(options) {
4726
+ this.options = options;
4727
+ }
4728
+ /**
4729
+ * Upload binary data to the raw storage bucket with retry logic.
4730
+ */
4731
+ async uploadRaw(blobName, data, metadata, maxRetries = 3) {
4732
+ const fileRef = (0, import_storage3.ref)(this.options.storageRaw, blobName);
4733
+ let attempt = 0;
4734
+ let lastError;
4735
+ while (attempt < maxRetries) {
4736
+ try {
4737
+ await new Promise((resolve2, reject) => {
4738
+ const task = (0, import_storage3.uploadBytesResumable)(fileRef, data, {
4739
+ customMetadata: metadata
4740
+ });
4741
+ task.on("state_changed", null, reject, resolve2);
4742
+ });
4743
+ return;
4744
+ } catch (err) {
4745
+ lastError = err;
4746
+ attempt++;
4747
+ if (attempt < maxRetries) {
4748
+ await new Promise((res) => setTimeout(res, 1e3 * attempt));
4749
+ }
4750
+ }
4751
+ }
4752
+ throw lastError;
4753
+ }
4754
+ /**
4755
+ * Upload data to the processed storage bucket.
4756
+ * Skips upload and returns `false` if the blob already exists.
4757
+ */
4758
+ async uploadProcessed(blobName, data, metadata) {
4759
+ const fileRef = (0, import_storage3.ref)(this.options.storageProcessed, blobName);
4760
+ const existing = await (0, import_storage3.getMetadata)(fileRef).catch(() => null);
4761
+ if (existing)
4762
+ return false;
4763
+ await (0, import_storage3.uploadBytes)(fileRef, data, { customMetadata: metadata });
4764
+ return true;
4765
+ }
4766
+ /**
4767
+ * List all blob names directly under `prefix` in the raw storage bucket.
4768
+ */
4769
+ async listRaw(prefix) {
4770
+ const listRef = (0, import_storage3.ref)(this.options.storageRaw, prefix);
4771
+ const result = await (0, import_storage3.listAll)(listRef);
4772
+ return result.items.map((item) => item.name);
4773
+ }
4774
+ };
4503
4775
 
4504
- :tdb_dataset_readwrite a tdb2:DatasetTDB2;
4505
- tdb2:location "${base}/databases/${partition}/tdb";
4506
- tdb2:unionDefaultGraph true ;
4507
- tdb2:transactionMode "transactional" ;
4508
- tdb2:transactionLogType "journal" ;
4509
- tdb2:blockFileSize "32M" ;
4510
- tdb2:blockCacheSize "1G" ;
4511
- tdb2:fileMode "direct" .
4512
-
4513
- :service_tdb_all a fuseki:Service;
4514
- fuseki:name "${partition}" ;
4515
- rdfs:label "TDB2 ${partition}";
4516
- fuseki:dataset :tdb_dataset_readwrite;
4517
- fuseki:allowedUsers "admin";
4518
- fuseki:endpoint [ fuseki:name "update";
4519
- fuseki:operation fuseki:update
4520
- ];
4521
- fuseki:endpoint [ fuseki:name "query";
4522
- fuseki:operation fuseki:query
4523
- ];
4524
- fuseki:endpoint [ fuseki:name "get";
4525
- fuseki:operation fuseki:gsp-r
4526
- ];
4527
- fuseki:endpoint [ fuseki:name "shacl";
4528
- fuseki:operation fuseki:shacl
4529
- ];
4530
- fuseki:endpoint [ fuseki:name "data";
4531
- fuseki:operation fuseki:gsp-rw
4532
- ];
4533
- fuseki:endpoint [ fuseki:name "sparql";
4534
- fuseki:operation fuseki:query
4535
- ].`;
4536
- }
4537
-
4538
- // apps/desktop/cue-cli/src/helpers/graph-upload.ts
4539
- var import_auth8 = require("firebase/auth");
4540
- var import_fs5 = require("fs");
4541
- async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
4542
- const stream = (0, import_fs5.createReadStream)(filePath);
4543
- const token = await (0, import_auth8.getAuth)().currentUser?.getIdToken();
4544
- const dataUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", "/data");
4545
- const headers = {
4546
- "x-project-id": id,
4547
- authorization: `Bearer ${token}`,
4548
- "Content-Type": mimeType
4549
- };
4550
- if (zipped) {
4551
- headers["Content-Encoding"] = "gzip";
4552
- }
4553
- const res = await fetch(dataUrl, {
4554
- method: "POST",
4555
- headers,
4556
- body: stream,
4557
- duplex: "half"
4558
- });
4559
- const text = await res.text();
4560
- console.log(text);
4561
- if (!res.ok) {
4562
- console.error(`Failed to upload graph: ${res.statusText}`);
4563
- return;
4564
- }
4565
- }
4566
-
4567
- // apps/desktop/cue-cli/src/cue-cli-dump.ts
4568
- async function dumpHandler(options) {
4569
- const { space, verbose, emulators, jelly, query, load } = options;
4570
- try {
4571
- const { isSuperAdmin } = await authenticate(emulators, options.key, verbose);
4572
- if (!isSuperAdmin) {
4573
- console.error("Sorry - this tool is only for super admin users");
4574
- process.exit(1);
4575
- }
4576
- if (verbose)
4577
- console.info("Dumping graph \u23F3");
4578
- let filePath = "";
4579
- if (jelly) {
4580
- if (verbose)
4581
- console.info("Setting: Jelly format");
4582
- if (verbose)
4583
- console.time("Downloaded graph \u2705");
4584
- filePath = await dumpRdfGraphToFileJelly(space, emulators, verbose);
4585
- if (verbose)
4586
- console.timeEnd("Downloaded graph \u2705");
4587
- } else {
4588
- if (verbose)
4589
- console.time("Downloaded and zipped graph \u2705");
4590
- if (query) {
4591
- if (verbose)
4592
- console.info("Setting: Construct query");
4593
- filePath = await dumpRdfGraphToFileConstruct(space, emulators, verbose);
4594
- } else {
4595
- if (verbose)
4596
- console.info("Setting: /data endpoint");
4597
- filePath = await dumpRdfGraphToFile(space, emulators, verbose);
4598
- }
4599
- if (verbose)
4600
- console.timeEnd("Downloaded and zipped graph \u2705");
4601
- }
4602
- console.info(`File written: ${filePath}`);
4603
- if (load && !emulators) {
4604
- if (verbose)
4605
- console.info(`Creating local RDF graph with id: ${space} \u23F3`);
4606
- await createGraph(space);
4607
- if (verbose)
4608
- console.info(`Created local RDF graph \u2705`);
4609
- if (verbose)
4610
- console.info(`Uploading file to local RDF graph \u23F3`);
4611
- await uploadToLocalGraph(
4612
- space,
4613
- filePath,
4614
- jelly ? "application/jelly+json" : "application/n-quads",
4615
- jelly ? false : true
4616
- );
4617
- if (verbose)
4618
- console.info(`Uploaded file to local RDF graph \u2705`);
4619
- }
4620
- } catch (err) {
4621
- console.error("Error:", err);
4622
- process.exit(1);
4623
- }
4624
- }
4625
-
4626
- // apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
4627
- var import_storage4 = require("firebase/storage");
4628
- async function repairRemoteTTL(space, subString, regex, substituteString) {
4629
- const firebase = CueFirebase.getInstance();
4630
- const storage = firebase.storageProcessed;
4631
- console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
4632
- const listResult = await (0, import_storage4.listAll)((0, import_storage4.ref)(storage, `${space}/triples`));
4633
- for (const fileRef of listResult.items) {
4634
- if (subString && !fileRef.name.match(subString))
4635
- continue;
4636
- const stream = await (0, import_storage4.getStream)(fileRef);
4637
- const reader = stream.getReader();
4638
- const chunks = [];
4639
- let done = false;
4640
- while (!done) {
4641
- const { value, done: streamDone } = await reader.read();
4642
- if (value) {
4643
- chunks.push(value);
4644
- }
4645
- done = streamDone;
4646
- }
4647
- let fileContent = Buffer.concat(chunks).toString("utf8");
4648
- let modified = false;
4649
- if (substituteString && regex) {
4650
- const newContent = fileContent.replace(regex, substituteString);
4651
- if (newContent !== fileContent) {
4652
- fileContent = newContent;
4653
- modified = true;
4654
- }
4655
- }
4656
- if (modified) {
4657
- const buffer = Buffer.from(fileContent, "utf8");
4658
- let existingMetadata = {};
4659
- try {
4660
- existingMetadata = await (0, import_storage4.getMetadata)(fileRef);
4661
- } catch (err) {
4662
- console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
4663
- }
4664
- const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
4665
- const metadata = { customMetadata };
4666
- await (0, import_storage4.uploadBytesResumable)((0, import_storage4.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
4667
- console.log(`Fixed ${fileRef.name} \u2705`);
4668
- } else {
4669
- console.log(`No changes for ${fileRef.name}`);
4670
- }
4671
- }
4672
- }
4673
-
4674
- // apps/desktop/cue-cli/src/cue-cli-repair-ttl.ts
4675
- async function repairTtlHandler(options) {
4676
- const { space, verbose, emulators, processor, from, to } = options;
4677
- try {
4678
- const { isSuperAdmin } = await authenticate(emulators, options.key, verbose);
4679
- if (!isSuperAdmin) {
4680
- console.error("Sorry - this tool is only for super admin users");
4681
- process.exit(1);
4682
- }
4683
- if (verbose)
4684
- console.info(`Repairing TTL files for processor ${processor} \u23F3`);
4685
- await repairRemoteTTL(space, processor, new RegExp(from, "g"), to);
4686
- } catch (err) {
4687
- console.error("Error:", err);
4688
- process.exit(1);
4689
- }
4690
- }
4691
-
4692
- // apps/desktop/cue-cli/src/helpers/upload-file.ts
4693
- var import_storage5 = require("firebase/storage");
4776
+ // libs/js/cue-sdk/src/lib/sync.ts
4777
+ var import_promises3 = require("fs/promises");
4778
+ var import_path3 = require("path");
4779
+ var import_module = require("module");
4694
4780
 
4695
4781
  // libs/js/models/src/lib/file-extensions.ts
4696
4782
  var fileExtensionsInfo = {
@@ -5774,121 +5860,935 @@ function uploadedFileMetadata(originalName, projectId, userId, md5, providerId,
5774
5860
  })
5775
5861
  };
5776
5862
  }
5777
- function turtleFileMetadata(sourceFile, processorId, locationUUID, stored = false) {
5778
- const ids = extractIdsFromRawPath(sourceFile);
5779
- const blob_name = locationUUID !== void 0 ? `${ids.projectId}/triples/${ids.documentUUID}_${locationUUID}_${processorId}.ttl` : `${ids.projectId}/triples/${ids.documentUUID}_${processorId}.ttl`;
5780
- const identifier = locationUUID !== void 0 ? `${ids.documentUUID}_${locationUUID}` : ids.documentUUID;
5781
- return {
5782
- blob_name,
5783
- processor: processorId,
5784
- space_id: ids.projectId,
5785
- stored: stored ? "True" : "False",
5786
- suffix: ".ttl",
5787
- identifier,
5788
- source: sourceFile
5863
+ function turtleFileMetadata(sourceFile, processorId, locationUUID, stored = false) {
5864
+ const ids = extractIdsFromRawPath(sourceFile);
5865
+ const blob_name = locationUUID !== void 0 ? `${ids.projectId}/triples/${ids.documentUUID}_${locationUUID}_${processorId}.ttl` : `${ids.projectId}/triples/${ids.documentUUID}_${processorId}.ttl`;
5866
+ const identifier = locationUUID !== void 0 ? `${ids.documentUUID}_${locationUUID}` : ids.documentUUID;
5867
+ return {
5868
+ blob_name,
5869
+ processor: processorId,
5870
+ space_id: ids.projectId,
5871
+ stored: stored ? "True" : "False",
5872
+ suffix: ".ttl",
5873
+ identifier,
5874
+ source: sourceFile
5875
+ };
5876
+ }
5877
+
5878
+ // libs/js/cue-sdk/src/lib/sync.ts
5879
+ var _scanFn = null;
5880
+ var _fileMapFn = null;
5881
+ var _wasmInitPromise = null;
5882
+ async function _initWasm() {
5883
+ const _require = (0, import_module.createRequire)((0, import_path3.resolve)(process.cwd(), "_anchor.js"));
5884
+ const pkgMain = _require.resolve("dir-scanner-wasm");
5885
+ const wasmBinary = await (0, import_promises3.readFile)((0, import_path3.join)((0, import_path3.dirname)(pkgMain), "dir_scanner_wasm_bg.wasm"));
5886
+ const mod = await import(
5887
+ /* @vite-ignore */
5888
+ pkgMain
5889
+ );
5890
+ await mod.default({ module_or_path: wasmBinary });
5891
+ _scanFn = mod.scan;
5892
+ _fileMapFn = mod.scan_file_map;
5893
+ }
5894
+ var DEFAULT_GRAPH_TYPE = "fuseki";
5895
+ var DEFAULT_SERVICE_ID = "cue-cli";
5896
+ var DEFAULT_RDF_BASE = "https://cue.qaecy.com/r/";
5897
+ var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5898
+ var ENDPOINT_QLEVER_QUERY = "/sparql/query";
5899
+ var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5900
+ var ENDPOINT_QLEVER_UPDATE = "/sparql/update";
5901
+ var CueSyncApi = class {
5902
+ constructor(_auth, _projects, _blob, _gatewayUrl, _serviceId = DEFAULT_SERVICE_ID, _rdfBase = DEFAULT_RDF_BASE) {
5903
+ this._auth = _auth;
5904
+ this._projects = _projects;
5905
+ this._blob = _blob;
5906
+ this._gatewayUrl = _gatewayUrl;
5907
+ this._serviceId = _serviceId;
5908
+ this._rdfBase = _rdfBase;
5909
+ }
5910
+ _graphMap = /* @__PURE__ */ new Map();
5911
+ async sync(localFiles, options) {
5912
+ const { spaceId, providerId, userId, verbose } = options;
5913
+ const token = await this._auth.getToken();
5914
+ if (!token)
5915
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5916
+ const graph = await this._getOrCreateGraph(spaceId, token);
5917
+ if (verbose)
5918
+ console.info("Listing remote files \u23F3");
5919
+ const remoteFiles = await this._listRemoteFiles(graph, spaceId, providerId, verbose);
5920
+ const report = await compareLocalRemote(localFiles, remoteFiles);
5921
+ if (verbose) {
5922
+ console.info(`Total local files: ${localFiles.length}`);
5923
+ console.info(`Total remote files: ${remoteFiles.length}`);
5924
+ console.info(
5925
+ `Total files to sync: ${(report.localNotOnRemote?.length ?? 0) + report.localNotOnRemotePathOnly.length}`
5926
+ );
5927
+ }
5928
+ let syncCount = report.syncCount;
5929
+ let syncSize = report.syncSize;
5930
+ let failedUploads = 0;
5931
+ let rdfWritten = false;
5932
+ let unitsConsumed = 0;
5933
+ let fileUnits = {};
5934
+ try {
5935
+ if (!_wasmInitPromise)
5936
+ _wasmInitPromise = _initWasm();
5937
+ await _wasmInitPromise;
5938
+ const entries = await Promise.all(
5939
+ localFiles.map(async (f) => ({
5940
+ originalPath: f.relativePath,
5941
+ data: new Uint8Array(await (0, import_promises3.readFile)(f.fullPath))
5942
+ }))
5943
+ );
5944
+ fileUnits = _fileMapFn(entries);
5945
+ } catch (err) {
5946
+ console.warn("[CueSyncApi] Unit map failed, proceeding without unit tracking:", err);
5947
+ }
5948
+ const toUpload = report.localNotOnRemote ?? [];
5949
+ if (verbose && toUpload.length)
5950
+ console.info("Syncing missing files \u23F3");
5951
+ for (const file of toUpload) {
5952
+ let uploadSucceeded = false;
5953
+ try {
5954
+ const rawMeta = uploadedFileMetadata(
5955
+ file.relativePath,
5956
+ spaceId,
5957
+ userId,
5958
+ file.md5,
5959
+ providerId
5960
+ );
5961
+ if (!rawMeta.blob_name)
5962
+ throw new Error(`blob_name missing for ${file.relativePath}`);
5963
+ const fileBuffer = await (0, import_promises3.readFile)(file.fullPath);
5964
+ await this._blob.uploadRaw(
5965
+ rawMeta.blob_name,
5966
+ new Uint8Array(fileBuffer),
5967
+ rawMeta
5968
+ );
5969
+ const uploaded = await this._uploadRdfMetadata(file, rawMeta, verbose);
5970
+ if (uploaded)
5971
+ rdfWritten = true;
5972
+ syncCount += 1;
5973
+ syncSize += file.size || 0;
5974
+ uploadSucceeded = true;
5975
+ this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, verbose);
5976
+ } catch (err) {
5977
+ failedUploads += 1;
5978
+ console.error(`[CueSyncApi] Failed to upload file: ${file.fullPath}`);
5979
+ if (verbose)
5980
+ console.error("[CueSyncApi] Upload error details:", err);
5981
+ }
5982
+ if (uploadSucceeded) {
5983
+ const fileUnitsCount = fileUnits[file.relativePath] ?? 0;
5984
+ if (fileUnitsCount > 0) {
5985
+ unitsConsumed += fileUnitsCount;
5986
+ try {
5987
+ await this._projects.incrementUnitsConsumed(spaceId, fileUnitsCount, userId);
5988
+ } catch (err) {
5989
+ console.error(`[CueSyncApi] Failed to write clientSync entry for ${file.relativePath}:`, err);
5990
+ }
5991
+ }
5992
+ }
5993
+ }
5994
+ if (verbose && report.localNotOnRemotePathOnly.length)
5995
+ console.info(`Syncing missing file locations (on provider "${providerId}") \u23F3`);
5996
+ for (const file of report.localNotOnRemotePathOnly) {
5997
+ const rawMeta = uploadedFileMetadata(
5998
+ file.relativePath,
5999
+ spaceId,
6000
+ userId,
6001
+ file.md5,
6002
+ providerId
6003
+ );
6004
+ if (!rawMeta.blob_name)
6005
+ throw new Error(`blob_name missing for ${file.relativePath}`);
6006
+ const uploaded = await this._uploadRdfMetadata(file, rawMeta, verbose);
6007
+ if (uploaded)
6008
+ rdfWritten = true;
6009
+ syncCount += 1;
6010
+ syncSize += file.size || 0;
6011
+ const fileUnitsCount = fileUnits[file.relativePath] ?? 0;
6012
+ if (fileUnitsCount > 0) {
6013
+ unitsConsumed += fileUnitsCount;
6014
+ try {
6015
+ await this._projects.incrementUnitsConsumed(spaceId, fileUnitsCount, userId);
6016
+ } catch (err) {
6017
+ console.error(`[CueSyncApi] Failed to write clientSync entry for ${file.relativePath}:`, err);
6018
+ }
6019
+ }
6020
+ this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, verbose);
6021
+ }
6022
+ return {
6023
+ syncCount,
6024
+ syncSize,
6025
+ failedUploads,
6026
+ totalCount: report.totalCount,
6027
+ totalSize: report.totalSize,
6028
+ rdfWritten,
6029
+ unitsConsumed
6030
+ };
6031
+ }
6032
+ async _getOrCreateGraph(spaceId, token) {
6033
+ const cached = this._graphMap.get(spaceId);
6034
+ if (cached)
6035
+ return cached;
6036
+ const project = await this._projects.getProject(spaceId);
6037
+ const graphType = project?.projectSettings?.graph?.type ?? DEFAULT_GRAPH_TYPE;
6038
+ const queryEndpoint = graphType === "qlever" ? `${this._gatewayUrl}${ENDPOINT_QLEVER_QUERY}` : `${this._gatewayUrl}${ENDPOINT_FUSEKI_QUERY}`;
6039
+ const updateEndpoint = graphType === "qlever" ? `${this._gatewayUrl}${ENDPOINT_QLEVER_UPDATE}` : `${this._gatewayUrl}${ENDPOINT_FUSEKI_UPDATE}`;
6040
+ const graph = new CueGraphDatabase({
6041
+ graphType,
6042
+ queryEndpoint,
6043
+ updateEndpoint,
6044
+ originalHeaders: {
6045
+ "x-project-id": spaceId,
6046
+ authorization: `Bearer ${token}`
6047
+ }
6048
+ });
6049
+ this._graphMap.set(spaceId, graph);
6050
+ return graph;
6051
+ }
6052
+ async _listRemoteFiles(graph, spaceId, providerId, verbose) {
6053
+ if (verbose)
6054
+ console.info(`Listing files in raw space: ${spaceId}`);
6055
+ const [blobNames, locationMap] = await Promise.all([
6056
+ this._blob.listRaw(spaceId),
6057
+ this._getGraphFiles(graph, providerId)
6058
+ ]);
6059
+ if (verbose)
6060
+ console.info(`Found ${blobNames.length} files in raw store for space: ${spaceId}`);
6061
+ const files = [];
6062
+ for (const name of blobNames) {
6063
+ const contentUUID = name.substring(0, 36);
6064
+ const locations = locationMap[contentUUID] ?? [];
6065
+ if (locations.length === 0) {
6066
+ if (verbose)
6067
+ console.warn(`No location data found for contentUUID: ${contentUUID}`);
6068
+ files.push({ contentUUID });
6069
+ } else {
6070
+ for (const loc of locations) {
6071
+ files.push({
6072
+ contentUUID,
6073
+ locationUUID: loc.locationUUID,
6074
+ created: loc.created,
6075
+ size: loc.size
6076
+ });
6077
+ }
6078
+ }
6079
+ }
6080
+ return files;
6081
+ }
6082
+ async _getGraphFiles(graph, providerId) {
6083
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
6084
+ SELECT ?fc ?loc ?created ?fp ?size
6085
+ WHERE {
6086
+ ?fc a qcy:FileContent ;
6087
+ qcy:sizeBytes ?size ;
6088
+ qcy:hasFileLocation ?loc .
6089
+ ?loc qcy:remoteProviderId "${providerId}" ;
6090
+ qcy:dateCreated ?created ;
6091
+ qcy:filePath ?fp .
6092
+ }`;
6093
+ const res = await graph.query(q, "application/sparql-results+json");
6094
+ const map = {};
6095
+ if (typeof res === "string")
6096
+ return map;
6097
+ for (const b of res.results.bindings) {
6098
+ const locationUUID = b["loc"].value.split("/").at(-1) ?? "";
6099
+ const contentUUID = b["fc"].value.split("/").at(-1) ?? "";
6100
+ const created = b["created"].value;
6101
+ const size = b["size"].value;
6102
+ if (!map[contentUUID])
6103
+ map[contentUUID] = [];
6104
+ map[contentUUID].push({ locationUUID, created, size });
6105
+ }
6106
+ return map;
6107
+ }
6108
+ async _uploadRdfMetadata(file, rawMeta, verbose) {
6109
+ if (!rawMeta.blob_name)
6110
+ throw new Error(`blob_name missing for ${file.relativePath}`);
6111
+ const ttlMeta = turtleFileMetadata(
6112
+ rawMeta.blob_name,
6113
+ this._serviceId,
6114
+ file.locationUUID
6115
+ );
6116
+ if (!ttlMeta.blob_name)
6117
+ throw new Error(`ttl blob_name missing for ${rawMeta.blob_name}`);
6118
+ const writer = fileLocationRaw(rawMeta, file.size);
6119
+ const namespace = `${this._rdfBase}${ttlMeta.space_id}/`;
6120
+ const triples = await serializeRDF(namespace, writer);
6121
+ const uploaded = await this._blob.uploadProcessed(
6122
+ ttlMeta.blob_name,
6123
+ new Uint8Array(Buffer.from(triples, "utf-8")),
6124
+ ttlMeta
6125
+ );
6126
+ if (!uploaded && verbose) {
6127
+ console.info(
6128
+ `Graph data for ${file.relativePath} already exists \u2014 skipping \u26A0\uFE0F`
6129
+ );
6130
+ }
6131
+ return uploaded;
6132
+ }
6133
+ /**
6134
+ * Scans `localFiles` and returns a per-extension cost breakdown.
6135
+ *
6136
+ * Each {@link ScanOutputRecord} contains `units` — the billable metric for
6137
+ * that extension (e.g. pages for PDFs, rows for spreadsheets) — which can be
6138
+ * shown to the user before or after calling {@link sync}.
6139
+ */
6140
+ async scanCost(localFiles) {
6141
+ if (!_wasmInitPromise)
6142
+ _wasmInitPromise = _initWasm();
6143
+ await _wasmInitPromise;
6144
+ const entries = await Promise.all(
6145
+ localFiles.map(async (f) => ({
6146
+ originalPath: f.relativePath,
6147
+ data: new Uint8Array(await (0, import_promises3.readFile)(f.fullPath))
6148
+ }))
6149
+ );
6150
+ return _scanFn(entries);
6151
+ }
6152
+ _logProgress(syncCount, totalCount, syncSize, totalSize, verbose) {
6153
+ if (!verbose || totalCount === 0)
6154
+ return;
6155
+ if (syncCount % Math.ceil(totalCount / 100) !== 0)
6156
+ return;
6157
+ const pct = Math.floor(syncCount / totalCount * 100);
6158
+ console.info(
6159
+ `Progress: ${pct}% (${syncCount}/${totalCount} files, ${syncSize}/${totalSize} bytes)`
6160
+ );
6161
+ }
6162
+ };
6163
+
6164
+ // libs/js/cue-sdk/src/lib/cue-node.ts
6165
+ var BUCKET_RAW2 = "spaces_raw_eu_west6";
6166
+ var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
6167
+ var CueNode = class extends Cue {
6168
+ constructor(config) {
6169
+ super(config);
6170
+ }
6171
+ _buildApi(projects) {
6172
+ const storageRaw = (0, import_storage4.getStorage)(this._app, BUCKET_RAW2);
6173
+ const storageProcessed = (0, import_storage4.getStorage)(this._app, BUCKET_PROCESSED2);
6174
+ if (this._isEmulator) {
6175
+ const storageHost = this._endpoints.storageEmulatorHost;
6176
+ const storagePort = this._endpoints.storageEmulatorPort;
6177
+ (0, import_storage4.connectStorageEmulator)(storageRaw, storageHost, storagePort);
6178
+ (0, import_storage4.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
6179
+ }
6180
+ const blob = new CueBlobStorage({ storageRaw, storageProcessed });
6181
+ const syncApi = new CueSyncApi(this.auth, projects, blob, this._endpoints.gatewayUrl);
6182
+ return new CueApi(this.auth, this._endpoints.gatewayUrl, projects, syncApi);
6183
+ }
6184
+ };
6185
+
6186
+ // apps/desktop/cue-cli/src/helpers/auth.ts
6187
+ async function authenticate(emulators, space, key, verbose = false) {
6188
+ if (!key) {
6189
+ key = process.env.CUE_API_KEY;
6190
+ if (!key) {
6191
+ console.error(
6192
+ "API key is required. Provide it via --key option or CUE_API_KEY environment variable."
6193
+ );
6194
+ process.exit(1);
6195
+ }
6196
+ }
6197
+ const cue = new CueNode({
6198
+ apiKey: FIREBASE_CONFIG().apiKey,
6199
+ appId: FIREBASE_CONFIG().appId,
6200
+ measurementId: FIREBASE_CONFIG().measurementId,
6201
+ environment: emulators ? "emulator" : "production"
6202
+ });
6203
+ if (verbose)
6204
+ console.info("Authenticating \u23F3");
6205
+ const user = await cue.auth.signInWithApiKey(key, space);
6206
+ if (verbose)
6207
+ console.info("Authenticated \u2705");
6208
+ const idTokenResult = await user.getIdTokenResult();
6209
+ const role = idTokenResult.claims["role"];
6210
+ const isSuperAdmin = role === "superadmin";
6211
+ return { isSuperAdmin, userId: user.uid };
6212
+ }
6213
+
6214
+ // apps/desktop/cue-cli/src/cue-cli-compare.ts
6215
+ async function compareHandler(options) {
6216
+ const { space, path, verbose, provider, emulators, zip } = options;
6217
+ try {
6218
+ await authenticate(emulators, space, options.key, verbose);
6219
+ if (verbose)
6220
+ console.info("Building compare base \u23F3");
6221
+ const qh = async (query3) => queryHandler(query3, space, emulators);
6222
+ const [localFiles, remoteFiles] = await Promise.all([
6223
+ listLocalFiles(
6224
+ path,
6225
+ provider,
6226
+ verbose,
6227
+ 5,
6228
+ IGNORED_LOCAL,
6229
+ HASH_WORKER_PATH,
6230
+ zip
6231
+ ),
6232
+ listRemoteFiles(space, provider, qh, verbose)
6233
+ ]);
6234
+ const unzipPromise = zip ? deleteUnzipped(path) : Promise.resolve();
6235
+ if (zip) {
6236
+ if (verbose)
6237
+ console.info("Started deletion of temp unzipped dirs \u23F3");
6238
+ }
6239
+ const report = await compareLocalRemote(localFiles, remoteFiles);
6240
+ await unzipPromise;
6241
+ if (zip && verbose)
6242
+ console.info("Cleaned up unzipped files \u2705");
6243
+ if (verbose)
6244
+ console.info("Built compare base \u2705");
6245
+ console.log("");
6246
+ console.log("--- Compare Report ---");
6247
+ console.log("");
6248
+ console.log(`Total files: ${report.totalCount}`);
6249
+ console.log(`Total size: ${fileSizePretty(report.totalSize || 0)}`);
6250
+ console.log("");
6251
+ console.log(
6252
+ `Files synchronized: ${report.syncCount} (${(report.synctPctCount * 100).toFixed(2)}%)`
6253
+ );
6254
+ console.log(
6255
+ `Synchronized size: ${fileSizePretty(report.syncSize || 0)} (${(report.synctPctSize * 100).toFixed(2)}%)`
6256
+ );
6257
+ console.log("");
6258
+ if (report.localNotOnRemote) {
6259
+ console.log(
6260
+ `${report.localNotOnRemote.length} files do not exist on remote`
6261
+ );
6262
+ if (verbose && report.localNotOnRemote.length > 0) {
6263
+ for (const f of report.localNotOnRemote) {
6264
+ console.log(
6265
+ " - " + f.relativePath + " (" + fileSizePretty(f.size || 0) + ")"
6266
+ );
6267
+ }
6268
+ }
6269
+ console.log("");
6270
+ }
6271
+ if (report.localNotOnRemotePathOnly) {
6272
+ console.log(
6273
+ `${report.localNotOnRemotePathOnly.length} file paths do not exist on remote on providerId "${provider}" (file duplicates)`
6274
+ );
6275
+ if (verbose && report.localNotOnRemotePathOnly.length > 0) {
6276
+ for (const f of report.localNotOnRemotePathOnly) {
6277
+ console.log(
6278
+ " - " + f.relativePath + " (" + fileSizePretty(f.size || 0) + ")"
6279
+ );
6280
+ }
6281
+ }
6282
+ console.log("");
6283
+ }
6284
+ if (report.remoteNotOnLocal) {
6285
+ console.log(`${report.remoteNotOnLocal.length} files do not exist locally`);
6286
+ console.log(
6287
+ "This might expected if the files belong to another provider or have been deleted locally"
6288
+ );
6289
+ if (verbose && report.remoteNotOnLocal.length > 0) {
6290
+ for (const f of report.remoteNotOnLocal) {
6291
+ console.log(
6292
+ " - " + f.contentUUID + " (" + fileSizePretty(f.size || 0) + ")"
6293
+ );
6294
+ }
6295
+ }
6296
+ console.log("");
6297
+ }
6298
+ if (report.remoteNotOnLocalPathOnly) {
6299
+ console.log(
6300
+ `${report.remoteNotOnLocalPathOnly.length} file paths on providerId "${provider}" do not exist locally`
6301
+ );
6302
+ console.log(
6303
+ "This might expected if the files belong to another provider or have been deleted locally"
6304
+ );
6305
+ if (verbose && report.remoteNotOnLocalPathOnly.length > 0) {
6306
+ for (const f of report.remoteNotOnLocalPathOnly) {
6307
+ console.log(
6308
+ " - " + f.contentUUID + " (" + fileSizePretty(f.size || 0) + ")"
6309
+ );
6310
+ }
6311
+ }
6312
+ console.log("");
6313
+ }
6314
+ } catch (err) {
6315
+ console.error("Error:", err);
6316
+ process.exit(1);
6317
+ }
6318
+ }
6319
+
6320
+ // apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
6321
+ var import_storage5 = require("firebase/storage");
6322
+ var import_path4 = require("path");
6323
+ var import_promises4 = require("fs/promises");
6324
+ async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
6325
+ const firebase = CueFirebase.getInstance();
6326
+ const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
6327
+ console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
6328
+ const listResult = await (0, import_storage5.listAll)((0, import_storage5.ref)(storage, subDir));
6329
+ const outputDir = (0, import_path4.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
6330
+ await (0, import_promises4.mkdir)(outputDir, { recursive: true });
6331
+ for (const fileRef of listResult.items) {
6332
+ console.log(fileRef.name);
6333
+ if (subDir && !fileRef.name.includes(subString))
6334
+ continue;
6335
+ const bytes = await (0, import_storage5.getBytes)(fileRef);
6336
+ const outputPath = (0, import_path4.join)(outputDir, fileRef.name);
6337
+ await (0, import_promises4.writeFile)(outputPath, Buffer.from(bytes));
6338
+ console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
6339
+ }
6340
+ }
6341
+
6342
+ // apps/desktop/cue-cli/src/cue-cli-dump-processed.ts
6343
+ async function dumpProcessedHandler(options) {
6344
+ const { space, verbose, emulators, processor } = options;
6345
+ try {
6346
+ const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
6347
+ if (!isSuperAdmin) {
6348
+ console.error("Sorry - this tool is only for super admin users");
6349
+ process.exit(1);
6350
+ }
6351
+ if (verbose)
6352
+ console.info(`Dumping processed files for processor ${processor} \u23F3`);
6353
+ const files = await getFilesContainingSubstring("processed", `${space}/triples`, processor);
6354
+ console.log(files);
6355
+ } catch (err) {
6356
+ console.error("Error:", err);
6357
+ process.exit(1);
6358
+ }
6359
+ }
6360
+
6361
+ // apps/desktop/cue-cli/src/helpers/graph-dump.ts
6362
+ var import_fs2 = require("fs");
6363
+ var import_stream = require("stream");
6364
+ var import_fs3 = require("fs");
6365
+ var import_zlib = require("zlib");
6366
+ var import_promises5 = require("stream/promises");
6367
+ var import_promises6 = require("fs/promises");
6368
+ var import_auth9 = require("firebase/auth");
6369
+
6370
+ // libs/js/size-tools/src/lib/js-size-tools.ts
6371
+ function humanFileSize(bytes, si = false, dp = 1) {
6372
+ const thresh = si ? 1e3 : 1024;
6373
+ if (Math.abs(bytes) < thresh) {
6374
+ return bytes + " B";
6375
+ }
6376
+ const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
6377
+ let u = -1;
6378
+ const r = 10 ** dp;
6379
+ do {
6380
+ bytes /= thresh;
6381
+ ++u;
6382
+ } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
6383
+ return bytes.toFixed(dp) + " " + units[u];
6384
+ }
6385
+
6386
+ // apps/desktop/cue-cli/src/helpers/graph-dump.ts
6387
+ var import_fs4 = require("fs");
6388
+ async function dumpRdfGraphToFileJelly(spaceId, useEmulator = false, verbose = false) {
6389
+ return dumpRdfGraphToFile(
6390
+ spaceId,
6391
+ useEmulator,
6392
+ verbose,
6393
+ `${spaceId}.jelly`,
6394
+ "application/x-jelly-rdf"
6395
+ );
6396
+ }
6397
+ async function dumpRdfGraphToFile(spaceId, useEmulator = false, verbose = false, outFile, mimeType = "application/n-quads") {
6398
+ const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
6399
+ const dataUrl = endpoint.replace("/query", "/data");
6400
+ if (verbose)
6401
+ console.info(`Streaming RDF graph \u23F3`);
6402
+ const filePath = outFile || `${spaceId}.nq`;
6403
+ if ((0, import_fs4.existsSync)(filePath) || (0, import_fs4.existsSync)(`${filePath}.gz`)) {
6404
+ if (verbose)
6405
+ console.info(`File ${filePath} already exists, skipping download.`);
6406
+ return mimeType === "application/n-quads" ? `${filePath}.gz` : filePath;
6407
+ }
6408
+ const token = await (0, import_auth9.getAuth)().currentUser?.getIdToken();
6409
+ const res = await fetch(dataUrl, {
6410
+ method: "GET",
6411
+ headers: {
6412
+ "x-project-id": spaceId,
6413
+ authorization: `Bearer ${token}`,
6414
+ Accept: mimeType
6415
+ }
6416
+ });
6417
+ if (!res.ok || !res.body) {
6418
+ throw new Error(`Failed to stream data: ${res.status} ${res.statusText}`);
6419
+ }
6420
+ const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
6421
+ const nodeStream = import_stream.Readable.fromWeb(res.body);
6422
+ let chunkCount = 0;
6423
+ let totalSize = 0;
6424
+ let hasDataTimeout = null;
6425
+ let lastChunkTime = Date.now();
6426
+ nodeStream.on("data", (chunk) => {
6427
+ lastChunkTime = Date.now();
6428
+ chunkCount++;
6429
+ totalSize += chunk.length;
6430
+ if (hasDataTimeout) {
6431
+ clearTimeout(hasDataTimeout);
6432
+ }
6433
+ if (verbose && totalSize >= 10 * 1024 * 1024 && totalSize % (10 * 1024 * 1024) < chunk.length)
6434
+ console.info(
6435
+ `Writing chunk #${chunkCount}, size: ${humanFileSize(totalSize)}`
6436
+ );
6437
+ });
6438
+ const streamTimeout = 3e5;
6439
+ hasDataTimeout = setTimeout(() => {
6440
+ const timeSinceLastChunk = Date.now() - lastChunkTime;
6441
+ if (timeSinceLastChunk > streamTimeout) {
6442
+ console.error(`Stream appears stalled - no data received for ${timeSinceLastChunk}ms`);
6443
+ nodeStream.destroy(new Error("Stream timeout"));
6444
+ }
6445
+ }, streamTimeout);
6446
+ try {
6447
+ await (0, import_promises5.pipeline)(nodeStream, fileStream);
6448
+ } finally {
6449
+ if (hasDataTimeout) {
6450
+ clearTimeout(hasDataTimeout);
6451
+ }
6452
+ }
6453
+ if (verbose)
6454
+ console.info(`Total chunks written: ${chunkCount}, total size: ${humanFileSize(totalSize)}`);
6455
+ if (totalSize === 0) {
6456
+ throw new Error("No data received from server - possible authentication or permission issue");
6457
+ }
6458
+ if (verbose)
6459
+ console.info(`Streamed RDF graph to ${filePath} \u2705`);
6460
+ if (mimeType === "application/n-quads") {
6461
+ if (verbose)
6462
+ console.info(`
6463
+ Gzipping to ${filePath}.gz \u23F3`);
6464
+ await _doGzip(filePath);
6465
+ if (verbose)
6466
+ console.info(`Gzipped to ${filePath}.gz \u2705`);
6467
+ return `${filePath}.gz`;
6468
+ }
6469
+ return filePath;
6470
+ }
6471
+ async function dumpRdfGraphToFileConstruct(spaceId, useEmulator = false, verbose = false, graphIRI, batchSize = 5e4, concurrency = 8, maxRetries = 5, retryDelayMs = 1e3, delayBetweenBatchesMs = 500) {
6472
+ const endpoint = useEmulator ? SPARQL_ENDPOINT_EMULATOR : SPARQL_ENDPOINT;
6473
+ const filePath = `${spaceId}.nq`;
6474
+ const graph = graphIRI !== void 0 ? `<${graphIRI}>` : "?g";
6475
+ const constructQuery = `
6476
+ CONSTRUCT { GRAPH ${graph} { ?s ?p ?o } }
6477
+ WHERE { GRAPH ${graph} { ?s ?p ?o } }
6478
+ `;
6479
+ let offset = 0;
6480
+ const fileStream = (0, import_fs2.createWriteStream)(filePath, { flags: "w" });
6481
+ let hasMore = true;
6482
+ while (hasMore) {
6483
+ const queries = Array.from(
6484
+ { length: concurrency },
6485
+ (_, i) => `${constructQuery} LIMIT ${batchSize} OFFSET ${offset + i * batchSize}`
6486
+ );
6487
+ offset += batchSize * concurrency;
6488
+ const results = await Promise.all(
6489
+ queries.map(
6490
+ (q, idx) => retryWithBackoff(
6491
+ () => _doQuery(q, spaceId, endpoint),
6492
+ maxRetries,
6493
+ retryDelayMs,
6494
+ verbose ? `Batch ${offset - batchSize * concurrency + idx * batchSize}` : void 0
6495
+ )
6496
+ )
6497
+ );
6498
+ const batches = results.filter(Boolean);
6499
+ if (batches.length === 0) {
6500
+ hasMore = false;
6501
+ break;
6502
+ }
6503
+ for (const batch of batches) {
6504
+ fileStream.write(batch.trim() + "\n");
6505
+ }
6506
+ if (verbose)
6507
+ process.stdout.write(`Current offset: ${offset.toLocaleString()}\r`);
6508
+ if (delayBetweenBatchesMs > 0)
6509
+ await new Promise((res) => setTimeout(res, delayBetweenBatchesMs));
6510
+ }
6511
+ fileStream.end();
6512
+ if (verbose)
6513
+ console.info(`
6514
+ Gzipping to ${filePath}.gz \u23F3`);
6515
+ await _doGzip(filePath);
6516
+ if (verbose)
6517
+ console.info(`Gzipped to ${filePath}.gz \u2705`);
6518
+ return `${filePath}.gz`;
6519
+ }
6520
+ async function retryWithBackoff(fn, maxRetries, delayMs, label) {
6521
+ let attempt = 0;
6522
+ let lastErr = null;
6523
+ while (attempt < maxRetries) {
6524
+ try {
6525
+ const result = await fn();
6526
+ if (result !== null && result !== void 0)
6527
+ return result;
6528
+ } catch (err) {
6529
+ lastErr = err;
6530
+ }
6531
+ attempt++;
6532
+ if (label)
6533
+ console.warn(`${label}: Retry ${attempt}/${maxRetries}`);
6534
+ await new Promise(
6535
+ (res) => setTimeout(res, delayMs * Math.pow(2, attempt - 1))
6536
+ );
6537
+ }
6538
+ if (label)
6539
+ console.error(`${label}: Failed after ${maxRetries} retries`, lastErr);
6540
+ return null;
6541
+ }
6542
+ async function _doGzip(filePath) {
6543
+ const gzFilePath = `${filePath}.gz`;
6544
+ await new Promise((resolve2, reject) => {
6545
+ const gzip = (0, import_zlib.createGzip)();
6546
+ const source = (0, import_fs3.createReadStream)(filePath);
6547
+ const dest = (0, import_fs2.createWriteStream)(gzFilePath);
6548
+ (0, import_promises5.pipeline)(source, gzip, dest).then(resolve2).catch(reject);
6549
+ });
6550
+ await (0, import_promises6.unlink)(filePath);
6551
+ }
6552
+ async function _doQuery(query3, spaceId, url) {
6553
+ const token = await (0, import_auth9.getAuth)().currentUser?.getIdToken();
6554
+ try {
6555
+ const res = await fetch(url, {
6556
+ method: "POST",
6557
+ headers: {
6558
+ "x-project-id": spaceId,
6559
+ authorization: `Bearer ${token}`,
6560
+ "Content-Type": "application/sparql-query",
6561
+ Accept: "application/n-quads"
6562
+ },
6563
+ body: query3
6564
+ });
6565
+ if (!res.ok) {
6566
+ console.error(`Error: ${res.status} ${res.statusText}`);
6567
+ return null;
6568
+ }
6569
+ return await res.text();
6570
+ } catch (e) {
6571
+ console.error(e);
6572
+ return null;
6573
+ }
6574
+ }
6575
+
6576
+ // apps/desktop/cue-cli/src/helpers/graph-create.ts
6577
+ var import_auth10 = require("firebase/auth");
6578
+ async function createGraph(id) {
6579
+ const token = await (0, import_auth10.getAuth)().currentUser?.getIdToken();
6580
+ const dataSetUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", `/$/datasets`);
6581
+ const res = await fetch(dataSetUrl, {
6582
+ method: "POST",
6583
+ headers: {
6584
+ "x-project-id": id,
6585
+ authorization: `Bearer ${token}`,
6586
+ "content-type": "text/turtle"
6587
+ },
6588
+ body: _getTemplate(id)
6589
+ });
6590
+ if (!res.ok) {
6591
+ throw new Error(`Failed to create graph: ${res.statusText}`);
6592
+ }
6593
+ }
6594
+ function _getTemplate(partition, base = "fuseki-base-new/fuseki") {
6595
+ return `@prefix : <http://base/#> .
6596
+ @prefix fuseki: <http://jena.apache.org/fuseki#> .
6597
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
6598
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
6599
+ @prefix tdb2: <http://jena.apache.org/2016/tdb#> .
6600
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
6601
+ @prefix text: <http://jena.apache.org/text#> .
6602
+ @prefix qcy: <${qaecyPrefixes["qcy"]}> .
6603
+ @prefix qcye: <${qaecyPrefixes["qcy-e"]}> .
6604
+
6605
+
6606
+ :tdb_dataset_readwrite a tdb2:DatasetTDB2;
6607
+ tdb2:location "${base}/databases/${partition}/tdb";
6608
+ tdb2:unionDefaultGraph true ;
6609
+ tdb2:transactionMode "transactional" ;
6610
+ tdb2:transactionLogType "journal" ;
6611
+ tdb2:blockFileSize "32M" ;
6612
+ tdb2:blockCacheSize "1G" ;
6613
+ tdb2:fileMode "direct" .
6614
+
6615
+ :service_tdb_all a fuseki:Service;
6616
+ fuseki:name "${partition}" ;
6617
+ rdfs:label "TDB2 ${partition}";
6618
+ fuseki:dataset :tdb_dataset_readwrite;
6619
+ fuseki:allowedUsers "admin";
6620
+ fuseki:endpoint [ fuseki:name "update";
6621
+ fuseki:operation fuseki:update
6622
+ ];
6623
+ fuseki:endpoint [ fuseki:name "query";
6624
+ fuseki:operation fuseki:query
6625
+ ];
6626
+ fuseki:endpoint [ fuseki:name "get";
6627
+ fuseki:operation fuseki:gsp-r
6628
+ ];
6629
+ fuseki:endpoint [ fuseki:name "shacl";
6630
+ fuseki:operation fuseki:shacl
6631
+ ];
6632
+ fuseki:endpoint [ fuseki:name "data";
6633
+ fuseki:operation fuseki:gsp-rw
6634
+ ];
6635
+ fuseki:endpoint [ fuseki:name "sparql";
6636
+ fuseki:operation fuseki:query
6637
+ ].`;
6638
+ }
6639
+
6640
+ // apps/desktop/cue-cli/src/helpers/graph-upload.ts
6641
+ var import_auth11 = require("firebase/auth");
6642
+ var import_fs5 = require("fs");
6643
+ async function uploadToLocalGraph(id, filePath, mimeType = "application/n-quads", zipped = false) {
6644
+ const stream = (0, import_fs5.createReadStream)(filePath);
6645
+ const token = await (0, import_auth11.getAuth)().currentUser?.getIdToken();
6646
+ const dataUrl = SPARQL_ENDPOINT_EMULATOR.replace("/query", "/data");
6647
+ const headers = {
6648
+ "x-project-id": id,
6649
+ authorization: `Bearer ${token}`,
6650
+ "Content-Type": mimeType
5789
6651
  };
6652
+ if (zipped) {
6653
+ headers["Content-Encoding"] = "gzip";
6654
+ }
6655
+ const res = await fetch(dataUrl, {
6656
+ method: "POST",
6657
+ headers,
6658
+ body: stream,
6659
+ duplex: "half"
6660
+ });
6661
+ const text = await res.text();
6662
+ console.log(text);
6663
+ if (!res.ok) {
6664
+ console.error(`Failed to upload graph: ${res.statusText}`);
6665
+ return;
6666
+ }
5790
6667
  }
5791
6668
 
5792
- // apps/desktop/cue-cli/src/helpers/upload-file.ts
5793
- var import_promises6 = require("fs/promises");
5794
- async function uploadFile(file, spaceId, userId, providerId) {
5795
- const firebase = CueFirebase.getInstance();
5796
- const rawFileMetadata = uploadedFileMetadata(
5797
- file.relativePath,
5798
- spaceId,
5799
- userId,
5800
- file.md5,
5801
- providerId
5802
- );
5803
- const storage = firebase.storageRaw;
5804
- const fileRef = (0, import_storage5.ref)(storage, rawFileMetadata.blob_name);
5805
- const fileBuffer = await (0, import_promises6.readFile)(file.fullPath);
5806
- const maxRetries = 3;
5807
- let attempt = 0;
5808
- let lastError = null;
5809
- while (attempt < maxRetries) {
5810
- try {
5811
- await new Promise((resolve2, reject) => {
5812
- const uploadTask = (0, import_storage5.uploadBytesResumable)(fileRef, fileBuffer, {
5813
- customMetadata: rawFileMetadata
5814
- });
5815
- uploadTask.on(
5816
- "state_changed",
5817
- null,
5818
- (error) => {
5819
- const blobName = rawFileMetadata.blob_name;
5820
- console.error("[uploadFile] Error uploading file:", {
5821
- filePath: file.fullPath,
5822
- relativePath: file.relativePath,
5823
- md5: file.md5,
5824
- blobName,
5825
- blobNameLength: blobName?.length,
5826
- blobNameIsUnusual: blobName && (blobName.length > 256 || /[^\w\-./]/.test(blobName)),
5827
- errorCode: error?.code,
5828
- errorMessage: error?.message,
5829
- errorPayload: error,
5830
- fileBufferSize: fileBuffer?.length,
5831
- rawFileMetadataKeys: Object.keys(rawFileMetadata),
5832
- rawFileMetadataLength: Object.keys(rawFileMetadata).length,
5833
- attempt
5834
- });
5835
- reject(error);
5836
- },
5837
- () => resolve2()
5838
- );
5839
- if (!uploadTask) {
5840
- console.error("[uploadFile] Upload task could not be created:", {
5841
- filePath: file.fullPath,
5842
- relativePath: file.relativePath,
5843
- md5: file.md5,
5844
- blobName: rawFileMetadata.blob_name,
5845
- attempt
5846
- });
5847
- reject(new Error("Upload task could not be created"));
5848
- }
5849
- });
5850
- lastError = null;
5851
- break;
5852
- } catch (err) {
5853
- lastError = err;
5854
- attempt++;
5855
- if (attempt < maxRetries) {
5856
- console.warn(`[uploadFile] Retry attempt ${attempt} for file: ${file.fullPath}`);
5857
- await new Promise((res) => setTimeout(res, 1e3 * attempt));
6669
+ // apps/desktop/cue-cli/src/cue-cli-dump.ts
6670
+ async function dumpHandler(options) {
6671
+ const { space, verbose, emulators, jelly, query: query3, load } = options;
6672
+ try {
6673
+ const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
6674
+ if (!isSuperAdmin) {
6675
+ console.error("Sorry - this tool is only for super admin users");
6676
+ process.exit(1);
6677
+ }
6678
+ if (verbose)
6679
+ console.info("Dumping graph \u23F3");
6680
+ let filePath = "";
6681
+ if (jelly) {
6682
+ if (verbose)
6683
+ console.info("Setting: Jelly format");
6684
+ if (verbose)
6685
+ console.time("Downloaded graph \u2705");
6686
+ filePath = await dumpRdfGraphToFileJelly(space, emulators, verbose);
6687
+ if (verbose)
6688
+ console.timeEnd("Downloaded graph \u2705");
6689
+ } else {
6690
+ if (verbose)
6691
+ console.time("Downloaded and zipped graph \u2705");
6692
+ if (query3) {
6693
+ if (verbose)
6694
+ console.info("Setting: Construct query");
6695
+ filePath = await dumpRdfGraphToFileConstruct(space, emulators, verbose);
6696
+ } else {
6697
+ if (verbose)
6698
+ console.info("Setting: /data endpoint");
6699
+ filePath = await dumpRdfGraphToFile(space, emulators, verbose);
5858
6700
  }
6701
+ if (verbose)
6702
+ console.timeEnd("Downloaded and zipped graph \u2705");
5859
6703
  }
6704
+ console.info(`File written: ${filePath}`);
6705
+ if (load && !emulators) {
6706
+ if (verbose)
6707
+ console.info(`Creating local RDF graph with id: ${space} \u23F3`);
6708
+ await createGraph(space);
6709
+ if (verbose)
6710
+ console.info(`Created local RDF graph \u2705`);
6711
+ if (verbose)
6712
+ console.info(`Uploading file to local RDF graph \u23F3`);
6713
+ await uploadToLocalGraph(
6714
+ space,
6715
+ filePath,
6716
+ jelly ? "application/jelly+json" : "application/n-quads",
6717
+ jelly ? false : true
6718
+ );
6719
+ if (verbose)
6720
+ console.info(`Uploaded file to local RDF graph \u2705`);
6721
+ }
6722
+ } catch (err) {
6723
+ console.error("Error:", err);
6724
+ process.exit(1);
5860
6725
  }
5861
- if (lastError) {
5862
- throw lastError;
5863
- }
5864
- return rawFileMetadata;
5865
6726
  }
5866
6727
 
5867
- // apps/desktop/cue-cli/src/helpers/upload-file-rdf.ts
6728
+ // apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
5868
6729
  var import_storage6 = require("firebase/storage");
5869
- async function uploadFileRDF(file, rawFileMetadata, verbose) {
6730
+ async function repairRemoteTTL(space, subString, regex, substituteString) {
5870
6731
  const firebase = CueFirebase.getInstance();
5871
- const ttlMetadata = turtleFileMetadata(
5872
- rawFileMetadata.blob_name,
5873
- SERVICE_ID,
5874
- file.locationUUID
5875
- );
5876
- const writer = fileLocationRaw(rawFileMetadata, file.size);
5877
- const namespace = `${RDF_BASE}${ttlMetadata.space_id}/`;
5878
- const triples = await serializeRDF(namespace, writer);
5879
6732
  const storage = firebase.storageProcessed;
5880
- const fileRef = (0, import_storage6.ref)(storage, ttlMetadata.blob_name);
5881
- const existing = await (0, import_storage6.getMetadata)(fileRef).catch(() => null);
5882
- if (existing) {
6733
+ console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
6734
+ const listResult = await (0, import_storage6.listAll)((0, import_storage6.ref)(storage, `${space}/triples`));
6735
+ for (const fileRef of listResult.items) {
6736
+ if (subString && !fileRef.name.match(subString))
6737
+ continue;
6738
+ const stream = await (0, import_storage6.getStream)(fileRef);
6739
+ const reader = stream.getReader();
6740
+ const chunks = [];
6741
+ let done = false;
6742
+ while (!done) {
6743
+ const { value, done: streamDone } = await reader.read();
6744
+ if (value) {
6745
+ chunks.push(value);
6746
+ }
6747
+ done = streamDone;
6748
+ }
6749
+ let fileContent = Buffer.concat(chunks).toString("utf8");
6750
+ let modified = false;
6751
+ if (substituteString && regex) {
6752
+ const newContent = fileContent.replace(regex, substituteString);
6753
+ if (newContent !== fileContent) {
6754
+ fileContent = newContent;
6755
+ modified = true;
6756
+ }
6757
+ }
6758
+ if (modified) {
6759
+ const buffer = Buffer.from(fileContent, "utf8");
6760
+ let existingMetadata = {};
6761
+ try {
6762
+ existingMetadata = await (0, import_storage6.getMetadata)(fileRef);
6763
+ } catch (err) {
6764
+ console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
6765
+ }
6766
+ const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
6767
+ const metadata = { customMetadata };
6768
+ await (0, import_storage6.uploadBytesResumable)((0, import_storage6.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
6769
+ console.log(`Fixed ${fileRef.name} \u2705`);
6770
+ } else {
6771
+ console.log(`No changes for ${fileRef.name}`);
6772
+ }
6773
+ }
6774
+ }
6775
+
6776
+ // apps/desktop/cue-cli/src/cue-cli-repair-ttl.ts
6777
+ async function repairTtlHandler(options) {
6778
+ const { space, verbose, emulators, processor, from, to } = options;
6779
+ try {
6780
+ const { isSuperAdmin } = await authenticate(emulators, space, options.key, verbose);
6781
+ if (!isSuperAdmin) {
6782
+ console.error("Sorry - this tool is only for super admin users");
6783
+ process.exit(1);
6784
+ }
5883
6785
  if (verbose)
5884
- console.info(
5885
- `Graph data ${file.relativePath} already exists but still in loading queue - skipping \u26A0\uFE0F`
5886
- );
5887
- return;
6786
+ console.info(`Repairing TTL files for processor ${processor} \u23F3`);
6787
+ await repairRemoteTTL(space, processor, new RegExp(from, "g"), to);
6788
+ } catch (err) {
6789
+ console.error("Error:", err);
6790
+ process.exit(1);
5888
6791
  }
5889
- await (0, import_storage6.uploadBytes)(fileRef, new Uint8Array(Buffer.from(triples, "utf-8")), {
5890
- customMetadata: ttlMetadata
5891
- });
5892
6792
  }
5893
6793
 
5894
6794
  // apps/desktop/cue-cli/src/helpers/emit-idle.ts
@@ -5934,15 +6834,36 @@ async function publishMessage(topicName, data, useEmulator) {
5934
6834
  // apps/desktop/cue-cli/src/cue-cli-sync.ts
5935
6835
  var import_promises7 = require("fs/promises");
5936
6836
  var import_fs6 = require("fs");
5937
- var import_path4 = require("path");
6837
+ var import_path5 = require("path");
6838
+ var readline = __toESM(require("readline"));
6839
+ function askConfirm(question) {
6840
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6841
+ return new Promise((resolve2) => {
6842
+ rl.question(question, (answer) => {
6843
+ rl.close();
6844
+ resolve2(answer.trim().toLowerCase() === "y");
6845
+ });
6846
+ });
6847
+ }
5938
6848
  async function syncHandler(options) {
5939
6849
  const { space, path, verbose, provider, emulators, zip } = options;
5940
6850
  try {
5941
- const { userId } = await authenticate(emulators, options.key, verbose);
6851
+ const cue = new CueNode({
6852
+ apiKey: FIREBASE_CONFIG().apiKey,
6853
+ appId: FIREBASE_CONFIG().appId,
6854
+ measurementId: FIREBASE_CONFIG().measurementId,
6855
+ environment: emulators ? "emulator" : "production"
6856
+ });
6857
+ const key = options.key ?? process.env.CUE_API_KEY;
6858
+ if (!key) {
6859
+ console.error(
6860
+ "API key is required. Provide it via --key option or CUE_API_KEY environment variable."
6861
+ );
6862
+ process.exit(1);
6863
+ }
5942
6864
  if (verbose)
5943
6865
  console.info("Building sync base \u23F3");
5944
- const qh = async (query) => queryHandler(query, space, emulators);
5945
- const resolvedPath = (0, import_path4.resolve)(path);
6866
+ const resolvedPath = (0, import_path5.resolve)(path);
5946
6867
  const pathStat = await (0, import_promises7.stat)(resolvedPath);
5947
6868
  const isFile = pathStat.isFile();
5948
6869
  let localFiles;
@@ -5950,18 +6871,20 @@ async function syncHandler(options) {
5950
6871
  if (verbose)
5951
6872
  console.info(`Path is a file, syncing single file: ${resolvedPath}`);
5952
6873
  const md5 = await fromReadStream((0, import_fs6.createReadStream)(resolvedPath));
5953
- const relativePath = (0, import_path4.basename)(resolvedPath);
6874
+ const relativePath = (0, import_path5.basename)(resolvedPath);
5954
6875
  const contentUUID = contextBasedGuid(md5);
5955
6876
  const locationUUID = generateFileUUID(relativePath, provider);
5956
- localFiles = [{
5957
- relativePath,
5958
- fullPath: resolvedPath,
5959
- md5,
5960
- contentUUID,
5961
- locationUUID,
5962
- mtimeMs: pathStat.mtimeMs,
5963
- size: pathStat.size
5964
- }];
6877
+ localFiles = [
6878
+ {
6879
+ relativePath,
6880
+ fullPath: resolvedPath,
6881
+ md5,
6882
+ contentUUID,
6883
+ locationUUID,
6884
+ mtimeMs: pathStat.mtimeMs,
6885
+ size: pathStat.size
6886
+ }
6887
+ ];
5965
6888
  } else {
5966
6889
  localFiles = await listLocalFiles(
5967
6890
  path,
@@ -5973,89 +6896,33 @@ async function syncHandler(options) {
5973
6896
  zip
5974
6897
  );
5975
6898
  }
5976
- const remoteFiles = await listRemoteFiles(space, provider, qh, verbose);
5977
- const report = await compareLocalRemote(localFiles, remoteFiles);
5978
- if (verbose) {
5979
- console.info("Built sync base \u2705");
5980
- console.info("");
5981
- console.info(`Total local files: ${localFiles.length}`);
5982
- console.info(`Total remote files: ${remoteFiles.length}`);
5983
- console.info(
5984
- `Total files to sync: ${report.localNotOnRemote.length + report.localNotOnRemotePathOnly.length}`
5985
- );
5986
- console.info("");
5987
- }
5988
- let syncCount = report.syncCount;
5989
- let syncSize = report.syncSize;
5990
- if (report.synctPctCount !== 0 && verbose) {
5991
- console.info(
5992
- `Synced percentage: ${Math.round(
5993
- report.synctPctCount * 100
5994
- )} % ( ${syncCount}/${report.totalCount} files )`
5995
- );
5996
- console.info(
5997
- `Synchronized size:: ${Math.round(
5998
- report.synctPctSize * 100
5999
- )}% ( ${fileSizePretty(syncSize)}/${fileSizePretty(report.totalSize)} )`
6000
- );
6001
- console.info("");
6002
- }
6003
- if (verbose && report.localNotOnRemote.length)
6004
- console.info("Syncing missing files \u23F3");
6005
- let rdfWritten = false;
6006
- let failedUploads = 0;
6007
- for (const file of report.localNotOnRemote) {
6008
- let rawFileMetadata;
6009
- try {
6010
- rawFileMetadata = await uploadFile(file, space, userId, provider);
6011
- await uploadFileRDF(file, rawFileMetadata, verbose);
6012
- syncCount += 1;
6013
- syncSize += file.size || 0;
6014
- const pct = Math.floor(syncCount / report.totalCount * 100);
6015
- if (verbose && report.totalCount > 0 && syncCount % Math.ceil(report.totalCount / 100) === 0) {
6016
- console.info(
6017
- `Progress: ${pct}% (${syncCount}/$${report.totalCount} files, ${fileSizePretty(syncSize)}/${fileSizePretty(
6018
- report.totalSize
6019
- )})`
6020
- );
6021
- }
6022
- rdfWritten = true;
6023
- } catch (err) {
6024
- failedUploads += 1;
6025
- console.error(`[syncHandler] Failed to upload file: ${file.fullPath}`);
6026
- if (verbose) {
6027
- console.error("[syncHandler] Upload error details:", err);
6028
- }
6029
- continue;
6030
- }
6899
+ if (verbose)
6900
+ console.info("Built sync base \u2705\n");
6901
+ const costRecords = await cue.api.sync.scanCost(localFiles);
6902
+ const totalUnits = costRecords.reduce((sum, r) => sum + r.units, 0);
6903
+ console.info("Cost estimate:");
6904
+ for (const r of costRecords) {
6905
+ console.info(` .${r.ext}: ${r.count} file(s) \u2192 ${r.units} unit(s)`);
6031
6906
  }
6032
- const zipDeletePromise = zip && !isFile ? deleteUnzipped(path) : Promise.resolve();
6033
- if (verbose && report.localNotOnRemotePathOnly.length)
6034
- console.info(
6035
- `Starting sync of missing file locations (on provider "${provider}") \u23F3`
6036
- );
6037
- for (const file of report.localNotOnRemotePathOnly) {
6038
- const rawFileMetadata = uploadedFileMetadata(
6039
- file.relativePath,
6040
- space,
6041
- userId,
6042
- file.md5,
6043
- provider
6044
- );
6045
- await uploadFileRDF(file, rawFileMetadata, verbose);
6046
- syncCount += 1;
6047
- syncSize += file.size || 0;
6048
- const pct = Math.floor(syncCount / report.totalCount * 100);
6049
- if (verbose && report.totalCount > 0 && syncCount % Math.ceil(report.totalCount / 100) === 0) {
6050
- console.info(
6051
- `Progress: ${pct}% (${syncCount}/$${report.totalCount} files, ${fileSizePretty(syncSize)}/${fileSizePretty(
6052
- report.totalSize
6053
- )})`
6054
- );
6055
- }
6056
- rdfWritten = true;
6907
+ console.info(` Total: ${totalUnits} unit(s) across ${localFiles.length} file(s)
6908
+ `);
6909
+ const confirmed = await askConfirm("Continue with sync? (y/n) ");
6910
+ if (!confirmed) {
6911
+ console.info("Sync cancelled.");
6912
+ process.exit(0);
6057
6913
  }
6058
- if (rdfWritten && emulators) {
6914
+ if (verbose)
6915
+ console.info("Authenticating \u23F3");
6916
+ const user = await cue.auth.signInWithApiKey(key, space);
6917
+ if (verbose)
6918
+ console.info("Authenticated \u2705\n");
6919
+ const result = await cue.api.sync.sync(localFiles, {
6920
+ spaceId: space,
6921
+ providerId: provider,
6922
+ userId: user.uid,
6923
+ verbose
6924
+ });
6925
+ if (result.rdfWritten && emulators) {
6059
6926
  if (verbose) {
6060
6927
  console.info("");
6061
6928
  console.info(`Throwing RDF_WRITING_IDLE topic (only in emulators) \u23F3`);
@@ -6071,18 +6938,23 @@ async function syncHandler(options) {
6071
6938
  console.warn("Continuing despite PubSub error in emulator mode...");
6072
6939
  }
6073
6940
  }
6941
+ const zipDeletePromise = zip && !isFile ? deleteUnzipped(path) : Promise.resolve();
6074
6942
  await zipDeletePromise;
6075
6943
  if (zip && verbose)
6076
6944
  console.info("Cleaned up unzipped files \u2705");
6077
6945
  if (verbose) {
6078
6946
  console.info("");
6947
+ console.info(
6948
+ `Synced: ${result.syncCount}/${result.totalCount} files (${fileSizePretty(result.syncSize)}/${fileSizePretty(result.totalSize)})`
6949
+ );
6079
6950
  console.info(`Sync finished \u{1F680}\u{1F680}\u{1F680}`);
6080
- if (failedUploads > 0) {
6081
- console.warn(`Total files failed to upload: ${failedUploads}`);
6951
+ if (result.failedUploads > 0) {
6952
+ console.warn(`Total files failed to upload: ${result.failedUploads}`);
6082
6953
  }
6083
6954
  }
6955
+ process.exit(0);
6084
6956
  } catch (err) {
6085
- console.error("Error:", err);
6957
+ console.error("[syncHandler] Unexpected error:", err);
6086
6958
  process.exit(1);
6087
6959
  }
6088
6960
  }
@@ -6270,10 +7142,10 @@ Triples only in file 2 (${result.triplesOnlyInFile2.size}):`);
6270
7142
  // apps/desktop/cue-cli/src/main.ts
6271
7143
  var packageJson;
6272
7144
  try {
6273
- packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path5.join)(__dirname, "package.json"), "utf8"));
7145
+ packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "package.json"), "utf8"));
6274
7146
  } catch {
6275
7147
  try {
6276
- packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path5.join)(__dirname, "../package.json"), "utf8"));
7148
+ packageJson = JSON.parse((0, import_fs9.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf8"));
6277
7149
  } catch {
6278
7150
  packageJson = { version: "0.0.0" };
6279
7151
  console.warn("Could not find package.json, using fallback version");