@qaecy/cue-sdk 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{cue-Cl66hB4E.js → cue-y-t8nSnt.js} +52 -45
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/api.d.ts +2 -1
- package/lib/models.d.ts +17 -2
- package/lib/sync.d.ts +21 -5
- package/node.js +228 -156
- package/package.json +1 -4
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { initializeApp as D } from "firebase/app";
|
|
2
|
-
import { getAuth as S, connectAuthEmulator as C, signInWithEmailAndPassword as
|
|
3
|
-
import { getFirestore as b, connectFirestoreEmulator as A, doc as _, collection as
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Q {
|
|
2
|
+
import { getAuth as S, connectAuthEmulator as C, signInWithEmailAndPassword as T, GoogleAuthProvider as P, OAuthProvider as U, signInWithPopup as O, signInWithCustomToken as j, signOut as v, onAuthStateChanged as N, fetchSignInMethodsForEmail as K, linkWithPopup as R, unlink as $, reauthenticateWithCredential as f, EmailAuthProvider as m, updatePassword as q, linkWithCredential as H, verifyBeforeUpdateEmail as x, sendEmailVerification as L } from "firebase/auth";
|
|
3
|
+
import { getFirestore as b, connectFirestoreEmulator as A, doc as _, collection as n, getDoc as y, setDoc as g, getDocs as h, query as l, where as d, serverTimestamp as F, increment as W, addDoc as z, deleteDoc as B, limit as J } from "firebase/firestore";
|
|
4
|
+
const M = "microsoft.com";
|
|
5
|
+
class V {
|
|
7
6
|
_auth;
|
|
8
7
|
_endpoints;
|
|
9
8
|
constructor(t, e = !1, r) {
|
|
@@ -15,14 +14,14 @@ class Q {
|
|
|
15
14
|
if (t === "password") {
|
|
16
15
|
if (!e)
|
|
17
16
|
throw new Error("credentials are required for password sign-in");
|
|
18
|
-
return (await
|
|
17
|
+
return (await T(
|
|
19
18
|
this._auth,
|
|
20
19
|
e.email,
|
|
21
20
|
e.password
|
|
22
21
|
)).user;
|
|
23
22
|
}
|
|
24
|
-
const r = t === "google" ? new P() : new U(
|
|
25
|
-
return (await
|
|
23
|
+
const r = t === "google" ? new P() : new U(M);
|
|
24
|
+
return (await O(this._auth, r)).user;
|
|
26
25
|
}
|
|
27
26
|
/** Sign in with a Cue API key */
|
|
28
27
|
async signInWithApiKey(t, e) {
|
|
@@ -36,11 +35,11 @@ class Q {
|
|
|
36
35
|
if (!r.ok)
|
|
37
36
|
throw new Error(`Failed to fetch custom token: ${r.statusText}`);
|
|
38
37
|
const { token: s } = await r.json();
|
|
39
|
-
return (await
|
|
38
|
+
return (await j(this._auth, s)).user;
|
|
40
39
|
}
|
|
41
40
|
/** Sign out the current user */
|
|
42
41
|
async signOut() {
|
|
43
|
-
await
|
|
42
|
+
await v(this._auth);
|
|
44
43
|
}
|
|
45
44
|
/** Currently signed-in user, or null if not authenticated */
|
|
46
45
|
get currentUser() {
|
|
@@ -48,7 +47,7 @@ class Q {
|
|
|
48
47
|
}
|
|
49
48
|
/** Subscribe to authentication state changes. Returns an unsubscribe function. */
|
|
50
49
|
onAuthStateChanged(t) {
|
|
51
|
-
return
|
|
50
|
+
return N(this._auth, t);
|
|
52
51
|
}
|
|
53
52
|
/** Get the Firebase ID token for the current user, or null if not authenticated */
|
|
54
53
|
async getToken(t = !1) {
|
|
@@ -60,7 +59,7 @@ class Q {
|
|
|
60
59
|
return this._auth;
|
|
61
60
|
}
|
|
62
61
|
}
|
|
63
|
-
const
|
|
62
|
+
const G = "/assistant/search", Q = "/triplestore/query", Y = "/data-views/admin/consumption/units";
|
|
64
63
|
class X {
|
|
65
64
|
constructor(t, e, r, s) {
|
|
66
65
|
this._auth = t, this._gatewayUrl = e, this.projects = r, this.sync = s;
|
|
@@ -89,7 +88,7 @@ class X {
|
|
|
89
88
|
* The user must be authenticated before calling this.
|
|
90
89
|
*/
|
|
91
90
|
async search(t) {
|
|
92
|
-
const e = await this._authHeaders(), r = await fetch(`${this._gatewayUrl}${
|
|
91
|
+
const e = await this._authHeaders(), r = await fetch(`${this._gatewayUrl}${G}`, {
|
|
93
92
|
method: "POST",
|
|
94
93
|
headers: e,
|
|
95
94
|
body: JSON.stringify({
|
|
@@ -107,7 +106,7 @@ class X {
|
|
|
107
106
|
* The user must be authenticated before calling this.
|
|
108
107
|
*/
|
|
109
108
|
async sparql(t, e) {
|
|
110
|
-
const r = await this._authHeaders(), s = await fetch(`${this._gatewayUrl}${
|
|
109
|
+
const r = await this._authHeaders(), s = await fetch(`${this._gatewayUrl}${Q}`, {
|
|
111
110
|
method: "POST",
|
|
112
111
|
headers: r,
|
|
113
112
|
body: JSON.stringify({ query: t, projectId: e })
|
|
@@ -116,6 +115,14 @@ class X {
|
|
|
116
115
|
throw new Error(`SPARQL query failed: ${s.status} ${s.statusText}`);
|
|
117
116
|
return s.json();
|
|
118
117
|
}
|
|
118
|
+
async getConsumption(t) {
|
|
119
|
+
const e = await this._authHeaders(), r = await fetch(`${this._gatewayUrl}${Y}`, {
|
|
120
|
+
headers: { ...e, "x-project-id": t }
|
|
121
|
+
});
|
|
122
|
+
if (!r.ok)
|
|
123
|
+
throw new Error(`Failed to fetch consumption: ${r.status} ${r.statusText}`);
|
|
124
|
+
return r.json();
|
|
125
|
+
}
|
|
119
126
|
}
|
|
120
127
|
const w = "projects";
|
|
121
128
|
class Z {
|
|
@@ -137,10 +144,10 @@ class Z {
|
|
|
137
144
|
* Throws if a project with the given ID already exists.
|
|
138
145
|
*/
|
|
139
146
|
async createProject(t) {
|
|
140
|
-
const e = this._requireUser(), r = t.id ?? crypto.randomUUID(), s = (/* @__PURE__ */ new Date()).toISOString(), a = { views: [], chatDisabled: !1 }, i = _(
|
|
141
|
-
if ((await
|
|
147
|
+
const e = this._requireUser(), r = t.id ?? crypto.randomUUID(), s = (/* @__PURE__ */ new Date()).toISOString(), a = { views: [], chatDisabled: !1 }, i = _(n(this._db, w), r);
|
|
148
|
+
if ((await y(i)).exists())
|
|
142
149
|
throw new Error(`Project with ID "${r}" already exists.`);
|
|
143
|
-
const
|
|
150
|
+
const u = {
|
|
144
151
|
id: r,
|
|
145
152
|
name: t.name,
|
|
146
153
|
organizationID: t.organizationID,
|
|
@@ -153,28 +160,28 @@ class Z {
|
|
|
153
160
|
alternativeIDs: [r],
|
|
154
161
|
projectSettings: a
|
|
155
162
|
};
|
|
156
|
-
return await
|
|
163
|
+
return await g(i, u), u;
|
|
157
164
|
}
|
|
158
165
|
/**
|
|
159
166
|
* List all projects where the authenticated user appears in the members, syncers, or admins array.
|
|
160
167
|
* Runs three parallel Firestore queries and deduplicates by project ID.
|
|
161
168
|
*/
|
|
162
169
|
async listProjects() {
|
|
163
|
-
const t = this._requireUser(), e =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
]), i = /* @__PURE__ */ new Set(),
|
|
168
|
-
for (const
|
|
169
|
-
for (const k of
|
|
170
|
+
const t = this._requireUser(), e = n(this._db, w), [r, s, a] = await Promise.all([
|
|
171
|
+
h(l(e, d("members", "array-contains", t))),
|
|
172
|
+
h(l(e, d("syncers", "array-contains", t))),
|
|
173
|
+
h(l(e, d("admins", "array-contains", t)))
|
|
174
|
+
]), i = /* @__PURE__ */ new Set(), o = [];
|
|
175
|
+
for (const u of [r, s, a])
|
|
176
|
+
for (const k of u.docs) {
|
|
170
177
|
const p = k.data();
|
|
171
|
-
i.has(p.id) || (i.add(p.id),
|
|
178
|
+
i.has(p.id) || (i.add(p.id), o.push(p));
|
|
172
179
|
}
|
|
173
|
-
return
|
|
180
|
+
return o;
|
|
174
181
|
}
|
|
175
182
|
/** Fetch a single project by ID. Returns null if not found. */
|
|
176
183
|
async getProject(t) {
|
|
177
|
-
const e = _(
|
|
184
|
+
const e = _(n(this._db, w), t), r = await y(e);
|
|
178
185
|
return r.exists() ? r.data() : null;
|
|
179
186
|
}
|
|
180
187
|
/**
|
|
@@ -184,14 +191,14 @@ class Z {
|
|
|
184
191
|
async incrementUnitsConsumed(t, e, r) {
|
|
185
192
|
if (e <= 0) return;
|
|
186
193
|
const s = _(this._db, "clientSync", t);
|
|
187
|
-
await
|
|
188
|
-
unitsConsumed:
|
|
189
|
-
lastUpdated:
|
|
194
|
+
await g(s, {
|
|
195
|
+
unitsConsumed: W(e),
|
|
196
|
+
lastUpdated: F(),
|
|
190
197
|
lastUserId: r
|
|
191
198
|
}, { merge: !0 });
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
|
-
const
|
|
201
|
+
const E = "apiKeys";
|
|
195
202
|
class tt {
|
|
196
203
|
constructor(t, e, r, s, a) {
|
|
197
204
|
this._auth = t, this._db = b(e), r && A(this._db, s, a);
|
|
@@ -208,7 +215,7 @@ class tt {
|
|
|
208
215
|
async getSignInMethods() {
|
|
209
216
|
const t = this._auth.currentUser;
|
|
210
217
|
if (!t?.email) throw new Error("User has no e-mail");
|
|
211
|
-
return
|
|
218
|
+
return K(this._auth.firebaseAuth, t.email);
|
|
212
219
|
}
|
|
213
220
|
/** Builds a human-readable label from a Firebase UserInfo provider entry. */
|
|
214
221
|
buildProviderLabel(t) {
|
|
@@ -228,16 +235,16 @@ class tt {
|
|
|
228
235
|
}
|
|
229
236
|
/** Unlinks a provider from the current account. */
|
|
230
237
|
async unlinkProvider(t) {
|
|
231
|
-
await
|
|
238
|
+
await $(this._requireUser(), t);
|
|
232
239
|
}
|
|
233
240
|
/** Changes the password. Reauthenticates first. */
|
|
234
241
|
async updatePassword(t, e) {
|
|
235
242
|
const r = this._requireUser();
|
|
236
243
|
if (!r.email) throw new Error("User has no e-mail");
|
|
237
|
-
await
|
|
244
|
+
await f(
|
|
238
245
|
r,
|
|
239
246
|
m.credential(r.email, t)
|
|
240
|
-
), await
|
|
247
|
+
), await q(r, e);
|
|
241
248
|
}
|
|
242
249
|
/** Adds (sets) a password for an account that currently only uses SSO. */
|
|
243
250
|
async addPassword(t) {
|
|
@@ -249,7 +256,7 @@ class tt {
|
|
|
249
256
|
async updateEmail(t, e) {
|
|
250
257
|
const r = this._requireUser();
|
|
251
258
|
if (!r.email) throw new Error("User e-mail not available");
|
|
252
|
-
await
|
|
259
|
+
await f(
|
|
253
260
|
r,
|
|
254
261
|
m.credential(r.email, e)
|
|
255
262
|
), await x(r, t), await L(r);
|
|
@@ -258,8 +265,8 @@ class tt {
|
|
|
258
265
|
async createAPIKey(t) {
|
|
259
266
|
const e = this._auth.currentUser?.uid;
|
|
260
267
|
if (!e) throw new Error("User not authenticated");
|
|
261
|
-
const r = { key: `cue-${
|
|
262
|
-
return this._apiKeyDocRef = await z(
|
|
268
|
+
const r = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", a = { key: `cue-${Array.from(globalThis.crypto.getRandomValues(new Uint8Array(150)), (o) => r[o & 63]).join("")}`, uid: e, expiration: t }, i = n(this._db, E);
|
|
269
|
+
return this._apiKeyDocRef = await z(i, a), a;
|
|
263
270
|
}
|
|
264
271
|
/** Revokes the current user's API key. */
|
|
265
272
|
async revokeAPIKey() {
|
|
@@ -269,11 +276,11 @@ class tt {
|
|
|
269
276
|
/** Fetches the current user's existing API key. */
|
|
270
277
|
async requestAPIKey() {
|
|
271
278
|
if (!this._apiKeyDocRef) throw new Error("No API key document reference");
|
|
272
|
-
const t = (await
|
|
279
|
+
const t = (await y(this._apiKeyDocRef)).data();
|
|
273
280
|
return { key: t.key, expiration: t.expiration };
|
|
274
281
|
}
|
|
275
282
|
async _checkIfUserHasAPIKey(t) {
|
|
276
|
-
const e =
|
|
283
|
+
const e = n(this._db, E), r = l(e, d("uid", "==", t), J(1)), s = await h(r);
|
|
277
284
|
return s.empty || (this._apiKeyDocRef = s.docs[0].ref), !s.empty;
|
|
278
285
|
}
|
|
279
286
|
_requireUser() {
|
|
@@ -302,7 +309,7 @@ const I = "qaecy-mvp-406413", et = "734737865998", rt = {
|
|
|
302
309
|
firestoreEmulatorPort: 8080
|
|
303
310
|
}
|
|
304
311
|
};
|
|
305
|
-
class
|
|
312
|
+
class ot {
|
|
306
313
|
auth;
|
|
307
314
|
api;
|
|
308
315
|
profile;
|
|
@@ -318,7 +325,7 @@ class nt {
|
|
|
318
325
|
authDomain: `${I}.firebaseapp.com`,
|
|
319
326
|
projectId: I,
|
|
320
327
|
messagingSenderId: et
|
|
321
|
-
}), this.auth = new
|
|
328
|
+
}), this.auth = new V(this._app, this._isEmulator, this._endpoints);
|
|
322
329
|
const r = new Z(this.auth, this._app, this._isEmulator, this._endpoints);
|
|
323
330
|
this.api = this._buildApi(r), this.profile = new tt(
|
|
324
331
|
this.auth,
|
|
@@ -338,9 +345,9 @@ class nt {
|
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
347
|
export {
|
|
341
|
-
|
|
348
|
+
ot as C,
|
|
342
349
|
X as a,
|
|
343
|
-
|
|
350
|
+
V as b,
|
|
344
351
|
tt as c,
|
|
345
352
|
Z as d
|
|
346
353
|
};
|
package/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { CueAuth } from './lib/auth';
|
|
|
3
3
|
export { CueApi } from './lib/api';
|
|
4
4
|
export { CueProjects } from './lib/project';
|
|
5
5
|
export { CueProfile } from './lib/profile';
|
|
6
|
-
export type { CueSdkConfig, SsoProvider, PasswordCredentials, SearchRequest, SearchResponse, SearchSource, ProjectData, ProjectSettings, CreateProjectOptions, SyncOptions, SyncResult, ScanOutputRecord, ProfileSSOAccount, APIKeyInfo, APIKeyDoc, } from './lib/models';
|
|
6
|
+
export type { CueSdkConfig, SsoProvider, PasswordCredentials, SearchRequest, SearchResponse, SearchSource, ProjectData, ProjectSettings, CreateProjectOptions, SyncOptions, SyncResult, SyncPreview, ScanOutputRecord, UnitsConsumedDto, ProfileSSOAccount, APIKeyInfo, APIKeyDoc, } from './lib/models';
|
|
7
7
|
export type { AuthStateListener, Unsubscribe } from './lib/auth';
|
package/index.js
CHANGED
package/lib/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CueAuth } from './auth';
|
|
2
|
-
import { SearchRequest, SearchResponse } from './models';
|
|
2
|
+
import { SearchRequest, SearchResponse, UnitsConsumedDto } from './models';
|
|
3
3
|
import { CueProjects } from './project';
|
|
4
4
|
import { CueSyncApi } from './sync';
|
|
5
5
|
export declare class CueApi {
|
|
@@ -24,4 +24,5 @@ export declare class CueApi {
|
|
|
24
24
|
* The user must be authenticated before calling this.
|
|
25
25
|
*/
|
|
26
26
|
sparql(query: string, projectId: string): Promise<unknown>;
|
|
27
|
+
getConsumption(projectId: string): Promise<UnitsConsumedDto>;
|
|
27
28
|
}
|
package/lib/models.d.ts
CHANGED
|
@@ -110,6 +110,23 @@ export interface ScanOutputRecord {
|
|
|
110
110
|
/** Total size of files with this extension in megabytes. */
|
|
111
111
|
sizeMb: number;
|
|
112
112
|
}
|
|
113
|
+
export interface UnitsConsumedDto {
|
|
114
|
+
unitsAvailable: number;
|
|
115
|
+
unitsConsumed: number;
|
|
116
|
+
filesProcessed: number;
|
|
117
|
+
}
|
|
118
|
+
export interface SyncPreview {
|
|
119
|
+
/** Per-extension cost breakdown for files not yet synced */
|
|
120
|
+
costRecords: ScanOutputRecord[];
|
|
121
|
+
/** Total units required for the new files */
|
|
122
|
+
unitsToConsume: number;
|
|
123
|
+
/** Units still available for this project */
|
|
124
|
+
unitsAvailable: number;
|
|
125
|
+
/** Number of new files that would be uploaded */
|
|
126
|
+
filesToUpload: number;
|
|
127
|
+
/** Total local files scanned */
|
|
128
|
+
totalLocalFiles: number;
|
|
129
|
+
}
|
|
113
130
|
export interface SyncResult {
|
|
114
131
|
/** Number of files successfully synced in this run */
|
|
115
132
|
syncCount: number;
|
|
@@ -123,8 +140,6 @@ export interface SyncResult {
|
|
|
123
140
|
totalSize: number;
|
|
124
141
|
/** Whether any RDF metadata was written */
|
|
125
142
|
rdfWritten: boolean;
|
|
126
|
-
/** Total billable units consumed across all successfully uploaded files in this run */
|
|
127
|
-
unitsConsumed: number;
|
|
128
143
|
}
|
|
129
144
|
export interface ProfileSSOAccount {
|
|
130
145
|
id: string;
|
package/lib/sync.d.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
import { CueBlobStorage } from 'js-databases';
|
|
2
2
|
import { LocalFile } from 'js-sync-tools';
|
|
3
3
|
import { CueAuth } from './auth';
|
|
4
|
+
import { CueApi } from './api';
|
|
4
5
|
import { CueProjects } from './project';
|
|
5
|
-
import { ScanOutputRecord, SyncOptions, SyncResult } from './models';
|
|
6
|
+
import { ScanOutputRecord, SyncOptions, SyncPreview, SyncResult } from './models';
|
|
6
7
|
export declare class CueSyncApi {
|
|
7
8
|
private readonly _auth;
|
|
8
9
|
private readonly _projects;
|
|
9
10
|
private readonly _blob;
|
|
10
11
|
private readonly _gatewayUrl;
|
|
11
|
-
private readonly _serviceId;
|
|
12
|
-
private readonly _rdfBase;
|
|
13
12
|
private readonly _graphMap;
|
|
14
|
-
|
|
13
|
+
private _api?;
|
|
14
|
+
private _pendingItems;
|
|
15
|
+
private _pendingSpaceId;
|
|
16
|
+
private _flushTimer;
|
|
17
|
+
constructor(_auth: CueAuth, _projects: CueProjects, _blob: CueBlobStorage, _gatewayUrl: string);
|
|
18
|
+
/** @internal Injected by CueApi after construction to avoid circular dependency. */
|
|
19
|
+
_bindApi(api: CueApi): void;
|
|
20
|
+
/**
|
|
21
|
+
* Returns a preview of what would be synced: cost breakdown for new files only,
|
|
22
|
+
* units required, and units still available. Use this before calling {@link sync}
|
|
23
|
+
* to show the user an accurate cost estimate.
|
|
24
|
+
*/
|
|
25
|
+
previewSync(localFiles: LocalFile[], options: SyncOptions): Promise<SyncPreview>;
|
|
15
26
|
sync(localFiles: LocalFile[], options: SyncOptions): Promise<SyncResult>;
|
|
16
27
|
private _getOrCreateGraph;
|
|
17
28
|
private _listRemoteFiles;
|
|
18
29
|
private _getGraphFiles;
|
|
19
|
-
private
|
|
30
|
+
private _initPendingBatch;
|
|
31
|
+
private _queueFileLocation;
|
|
32
|
+
private _drainPending;
|
|
33
|
+
private _flushBatch;
|
|
34
|
+
private _postFssBatch;
|
|
35
|
+
private _stopFlushTimer;
|
|
20
36
|
/**
|
|
21
37
|
* Scans `localFiles` and returns a per-extension cost breakdown.
|
|
22
38
|
*
|
package/node.js
CHANGED
|
@@ -1,161 +1,194 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { b as
|
|
3
|
-
import { getStorage as
|
|
4
|
-
import { CueGraphDatabase as
|
|
5
|
-
import { readFile as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { qaecyPrefixes as
|
|
12
|
-
let
|
|
13
|
-
async function
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { C as v, a as O } from "./cue-y-t8nSnt.js";
|
|
2
|
+
import { b as dt, c as _t, d as gt } from "./cue-y-t8nSnt.js";
|
|
3
|
+
import { getStorage as b, connectStorageEmulator as T } from "firebase/storage";
|
|
4
|
+
import { CueGraphDatabase as q, CueBlobStorage as z } from "js-databases";
|
|
5
|
+
import { readFile as P, writeFile as B, unlink as D } from "fs/promises";
|
|
6
|
+
import { join as y } from "path";
|
|
7
|
+
import { pathToFileURL as j } from "url";
|
|
8
|
+
import { tmpdir as x } from "os";
|
|
9
|
+
import { compareLocalRemote as A } from "js-sync-tools";
|
|
10
|
+
import { uploadedFileMetadata as L } from "js-file-metadata-helpers";
|
|
11
|
+
import { qaecyPrefixes as M } from "js/prefixes";
|
|
12
|
+
let C = null, $ = null;
|
|
13
|
+
async function k() {
|
|
14
|
+
const c = y(__dirname, "assets", "wasm"), t = await P(y(c, "dir_scanner_wasm_bg.wasm")), e = await import(j(y(c, "dir_scanner_wasm.mjs")).href);
|
|
15
|
+
await e.default({ module_or_path: t }), C = e.scan;
|
|
16
|
+
}
|
|
17
|
+
const H = "fuseki", G = "/triplestore/query", W = "/sparql/query", K = "/triplestore/update", J = "/sparql/update", Q = "/commands/file-system-structure/batch", F = 1e3, I = "cue:pending:";
|
|
18
|
+
function U(c) {
|
|
19
|
+
return y(x(), `cue-sync-pending-${c}.json`);
|
|
20
|
+
}
|
|
21
|
+
async function Y(c) {
|
|
22
|
+
if (typeof window < "u") {
|
|
23
|
+
const t = window.localStorage.getItem(`${I}${c}`);
|
|
24
|
+
return t ? JSON.parse(t) : null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const t = await P(U(c), "utf-8");
|
|
28
|
+
return JSON.parse(t);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function N(c) {
|
|
34
|
+
const t = JSON.stringify(c);
|
|
35
|
+
if (typeof window < "u") {
|
|
36
|
+
window.localStorage.setItem(`${I}${c.spaceId}`, t);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await B(U(c.spaceId), t, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
async function V(c) {
|
|
42
|
+
if (typeof window < "u") {
|
|
43
|
+
window.localStorage.removeItem(`${I}${c}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await D(U(c));
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
19
50
|
}
|
|
20
|
-
const x = "fuseki", H = "cue-cli", K = "https://cue.qaecy.com/r/", Q = "/triplestore/query", W = "/sparql/query", V = "/triplestore/update", Y = "/sparql/update";
|
|
21
51
|
class X {
|
|
22
|
-
constructor(t, o, e, i
|
|
23
|
-
this._auth = t, this._projects = o, this._blob = e, this._gatewayUrl = i
|
|
52
|
+
constructor(t, o, e, i) {
|
|
53
|
+
this._auth = t, this._projects = o, this._blob = e, this._gatewayUrl = i;
|
|
24
54
|
}
|
|
25
55
|
_auth;
|
|
26
56
|
_projects;
|
|
27
57
|
_blob;
|
|
28
58
|
_gatewayUrl;
|
|
29
|
-
_serviceId;
|
|
30
|
-
_rdfBase;
|
|
31
59
|
_graphMap = /* @__PURE__ */ new Map();
|
|
60
|
+
_api;
|
|
61
|
+
_pendingItems = [];
|
|
62
|
+
_pendingSpaceId = null;
|
|
63
|
+
_flushTimer = null;
|
|
64
|
+
/** @internal Injected by CueApi after construction to avoid circular dependency. */
|
|
65
|
+
_bindApi(t) {
|
|
66
|
+
this._api = t;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns a preview of what would be synced: cost breakdown for new files only,
|
|
70
|
+
* units required, and units still available. Use this before calling {@link sync}
|
|
71
|
+
* to show the user an accurate cost estimate.
|
|
72
|
+
*/
|
|
73
|
+
async previewSync(t, o) {
|
|
74
|
+
const { spaceId: e, providerId: i, verbose: s } = o, n = await this._auth.getToken();
|
|
75
|
+
if (!n) throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
76
|
+
const r = await this._getOrCreateGraph(e, n), [a, l] = await Promise.all([
|
|
77
|
+
this._listRemoteFiles(r, e, i, s),
|
|
78
|
+
this._api?.getConsumption(e) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
|
|
79
|
+
]), p = (await A(t, a)).localNotOnRemote ?? [], h = p.length > 0 ? await this.scanCost(p) : [], _ = h.reduce((g, m) => g + m.units, 0);
|
|
80
|
+
return {
|
|
81
|
+
costRecords: h,
|
|
82
|
+
unitsToConsume: _,
|
|
83
|
+
unitsAvailable: l.unitsAvailable,
|
|
84
|
+
filesToUpload: p.length,
|
|
85
|
+
totalLocalFiles: t.length
|
|
86
|
+
};
|
|
87
|
+
}
|
|
32
88
|
async sync(t, o) {
|
|
33
|
-
const { spaceId: e, providerId: i, userId: s, verbose:
|
|
34
|
-
if (!
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
89
|
+
const { spaceId: e, providerId: i, userId: s, verbose: n } = o, r = await this._auth.getToken();
|
|
90
|
+
if (!r) throw new Error("Not authenticated. Call cue.auth.signIn() first.");
|
|
91
|
+
const a = await this._getOrCreateGraph(e, r);
|
|
92
|
+
n && console.info("Listing remote files ⏳");
|
|
93
|
+
const [l, d] = await Promise.all([
|
|
94
|
+
this._listRemoteFiles(a, e, i, n),
|
|
95
|
+
this._api?.getConsumption(e) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
|
|
96
|
+
]), { unitsAvailable: p } = d, h = await A(t, l);
|
|
97
|
+
n && (console.info(`Total local files: ${t.length}`), console.info(`Total remote files: ${l.length}`), console.info(
|
|
98
|
+
`Total files to sync: ${(h.localNotOnRemote?.length ?? 0) + h.localNotOnRemotePathOnly.length}`
|
|
40
99
|
));
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
P = A(r);
|
|
51
|
-
} catch (r) {
|
|
52
|
-
console.warn("[CueSyncApi] Unit map failed, proceeding without unit tracking:", r);
|
|
100
|
+
let _ = h.syncCount, g = h.syncSize, m = 0, S = !1;
|
|
101
|
+
const w = h.localNotOnRemote ?? [];
|
|
102
|
+
if (w.length > 0) {
|
|
103
|
+
const f = (await this.scanCost(w)).reduce((E, R) => E + R.units, 0);
|
|
104
|
+
if (f > p)
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Insufficient units: ${f} units required but only ${p} available.`
|
|
107
|
+
);
|
|
53
108
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (const r of E) {
|
|
57
|
-
let h = !1;
|
|
109
|
+
await this._initPendingBatch(e, r, n), n && w.length && console.info("Syncing missing files ⏳");
|
|
110
|
+
for (const u of w)
|
|
58
111
|
try {
|
|
59
|
-
const f =
|
|
60
|
-
|
|
112
|
+
const f = L(
|
|
113
|
+
u.relativePath,
|
|
61
114
|
e,
|
|
62
115
|
s,
|
|
63
|
-
|
|
116
|
+
u.md5,
|
|
64
117
|
i
|
|
65
118
|
);
|
|
66
|
-
if (!f.blob_name) throw new Error(`blob_name missing for ${
|
|
67
|
-
const
|
|
119
|
+
if (!f.blob_name) throw new Error(`blob_name missing for ${u.relativePath}`);
|
|
120
|
+
const E = await P(u.fullPath);
|
|
68
121
|
await this._blob.uploadRaw(
|
|
69
122
|
f.blob_name,
|
|
70
|
-
new Uint8Array(
|
|
123
|
+
new Uint8Array(E),
|
|
71
124
|
f
|
|
72
|
-
), await this.
|
|
125
|
+
), await this._queueFileLocation({
|
|
126
|
+
relativePath: u.relativePath,
|
|
127
|
+
md5: u.md5,
|
|
128
|
+
size: u.size,
|
|
129
|
+
providerId: i,
|
|
130
|
+
fileContentExists: !1
|
|
131
|
+
}), S = !0, _ += 1, g += u.size || 0, this._logProgress(_, h.totalCount, g, h.totalSize, n);
|
|
73
132
|
} catch (f) {
|
|
74
|
-
|
|
133
|
+
m += 1, console.error(`[CueSyncApi] Failed to upload file: ${u.fullPath}`), n && console.error("[CueSyncApi] Upload error details:", f);
|
|
75
134
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
e,
|
|
93
|
-
s,
|
|
94
|
-
r.md5,
|
|
95
|
-
i
|
|
96
|
-
);
|
|
97
|
-
if (!h.blob_name) throw new Error(`blob_name missing for ${r.relativePath}`);
|
|
98
|
-
await this._uploadRdfMetadata(r, h, a) && (g = !0), u += 1, m += r.size || 0;
|
|
99
|
-
const d = P[r.relativePath] ?? 0;
|
|
100
|
-
if (d > 0) {
|
|
101
|
-
y += d;
|
|
102
|
-
try {
|
|
103
|
-
await this._projects.incrementUnitsConsumed(e, d, s);
|
|
104
|
-
} catch ($) {
|
|
105
|
-
console.error(`[CueSyncApi] Failed to write clientSync entry for ${r.relativePath}:`, $);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
this._logProgress(u, n.totalCount, m, n.totalSize, a);
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
syncCount: u,
|
|
112
|
-
syncSize: m,
|
|
113
|
-
failedUploads: U,
|
|
114
|
-
totalCount: n.totalCount,
|
|
115
|
-
totalSize: n.totalSize,
|
|
116
|
-
rdfWritten: g,
|
|
117
|
-
unitsConsumed: y
|
|
135
|
+
n && h.localNotOnRemotePathOnly.length && console.info(`Syncing missing file locations (on provider "${i}") ⏳`);
|
|
136
|
+
for (const u of h.localNotOnRemotePathOnly)
|
|
137
|
+
await this._queueFileLocation({
|
|
138
|
+
relativePath: u.relativePath,
|
|
139
|
+
md5: u.md5,
|
|
140
|
+
size: u.size,
|
|
141
|
+
providerId: i,
|
|
142
|
+
fileContentExists: !0
|
|
143
|
+
}), S = !0, _ += 1, g += u.size || 0, this._logProgress(_, h.totalCount, g, h.totalSize, n);
|
|
144
|
+
return await this._drainPending(n), this._stopFlushTimer(), {
|
|
145
|
+
syncCount: _,
|
|
146
|
+
syncSize: g,
|
|
147
|
+
failedUploads: m,
|
|
148
|
+
totalCount: h.totalCount,
|
|
149
|
+
totalSize: h.totalSize,
|
|
150
|
+
rdfWritten: S
|
|
118
151
|
};
|
|
119
152
|
}
|
|
120
153
|
async _getOrCreateGraph(t, o) {
|
|
121
154
|
const e = this._graphMap.get(t);
|
|
122
155
|
if (e) return e;
|
|
123
|
-
const s = (await this._projects.getProject(t))?.projectSettings?.graph?.type ??
|
|
156
|
+
const s = (await this._projects.getProject(t))?.projectSettings?.graph?.type ?? H, n = s === "qlever" ? `${this._gatewayUrl}${W}` : `${this._gatewayUrl}${G}`, r = s === "qlever" ? `${this._gatewayUrl}${J}` : `${this._gatewayUrl}${K}`, a = new q({
|
|
124
157
|
graphType: s,
|
|
125
|
-
queryEndpoint:
|
|
126
|
-
updateEndpoint:
|
|
158
|
+
queryEndpoint: n,
|
|
159
|
+
updateEndpoint: r,
|
|
127
160
|
originalHeaders: {
|
|
128
161
|
"x-project-id": t,
|
|
129
162
|
authorization: `Bearer ${o}`
|
|
130
163
|
}
|
|
131
164
|
});
|
|
132
|
-
return this._graphMap.set(t,
|
|
165
|
+
return this._graphMap.set(t, a), a;
|
|
133
166
|
}
|
|
134
167
|
async _listRemoteFiles(t, o, e, i) {
|
|
135
168
|
i && console.info(`Listing files in raw space: ${o}`);
|
|
136
|
-
const [s,
|
|
169
|
+
const [s, n] = await Promise.all([
|
|
137
170
|
this._blob.listRaw(o),
|
|
138
171
|
this._getGraphFiles(t, e)
|
|
139
172
|
]);
|
|
140
173
|
i && console.info(`Found ${s.length} files in raw store for space: ${o}`);
|
|
141
|
-
const
|
|
142
|
-
for (const
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
i && console.warn(`No location data found for contentUUID: ${
|
|
174
|
+
const r = [];
|
|
175
|
+
for (const a of s) {
|
|
176
|
+
const l = a.substring(0, 36), d = n[l] ?? [];
|
|
177
|
+
if (d.length === 0)
|
|
178
|
+
i && console.warn(`No location data found for contentUUID: ${l}`), r.push({ contentUUID: l });
|
|
146
179
|
else
|
|
147
|
-
for (const
|
|
148
|
-
|
|
149
|
-
contentUUID:
|
|
150
|
-
locationUUID:
|
|
151
|
-
created:
|
|
152
|
-
size:
|
|
180
|
+
for (const p of d)
|
|
181
|
+
r.push({
|
|
182
|
+
contentUUID: l,
|
|
183
|
+
locationUUID: p.locationUUID,
|
|
184
|
+
created: p.created,
|
|
185
|
+
size: p.size
|
|
153
186
|
});
|
|
154
187
|
}
|
|
155
|
-
return
|
|
188
|
+
return r;
|
|
156
189
|
}
|
|
157
190
|
async _getGraphFiles(t, o) {
|
|
158
|
-
const e = `PREFIX qcy: <${
|
|
191
|
+
const e = `PREFIX qcy: <${M.qcy}>
|
|
159
192
|
SELECT ?fc ?loc ?created ?fp ?size
|
|
160
193
|
WHERE {
|
|
161
194
|
?fc a qcy:FileContent ;
|
|
@@ -166,28 +199,60 @@ WHERE {
|
|
|
166
199
|
qcy:filePath ?fp .
|
|
167
200
|
}`, i = await t.query(e, "application/sparql-results+json"), s = {};
|
|
168
201
|
if (typeof i == "string") return s;
|
|
169
|
-
for (const
|
|
170
|
-
const
|
|
171
|
-
s[
|
|
202
|
+
for (const n of i.results.bindings) {
|
|
203
|
+
const r = n.loc.value.split("/").at(-1) ?? "", a = n.fc.value.split("/").at(-1) ?? "", l = n.created.value, d = n.size.value;
|
|
204
|
+
s[a] || (s[a] = []), s[a].push({ locationUUID: r, created: l, size: d });
|
|
172
205
|
}
|
|
173
206
|
return s;
|
|
174
207
|
}
|
|
175
|
-
async
|
|
176
|
-
|
|
177
|
-
const i =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
208
|
+
async _initPendingBatch(t, o, e) {
|
|
209
|
+
this._pendingSpaceId = t, this._pendingItems = [];
|
|
210
|
+
const i = await Y(t);
|
|
211
|
+
i && i.items.length > 0 && (e && console.info(`Flushing ${i.items.length} pending file location(s) from previous sync ⏳`), await this._flushBatch(i.items, t, o, e));
|
|
212
|
+
const s = setInterval(() => {
|
|
213
|
+
this._drainPending(e).catch(
|
|
214
|
+
(n) => console.error("[CueSyncApi] Periodic flush failed:", n)
|
|
215
|
+
);
|
|
216
|
+
}, 6e4);
|
|
217
|
+
typeof s == "object" && typeof s.unref == "function" && s.unref(), this._flushTimer = s;
|
|
218
|
+
}
|
|
219
|
+
async _queueFileLocation(t) {
|
|
220
|
+
this._pendingItems.push(t), this._pendingSpaceId && await N({ spaceId: this._pendingSpaceId, items: this._pendingItems });
|
|
221
|
+
}
|
|
222
|
+
async _drainPending(t) {
|
|
223
|
+
if (!this._pendingSpaceId || this._pendingItems.length === 0) return;
|
|
224
|
+
const o = await this._auth.getToken();
|
|
225
|
+
o && await this._flushBatch(this._pendingItems, this._pendingSpaceId, o, t);
|
|
226
|
+
}
|
|
227
|
+
async _flushBatch(t, o, e, i) {
|
|
228
|
+
const s = [...t];
|
|
229
|
+
this._pendingSpaceId === o && (this._pendingItems = []), await V(o);
|
|
230
|
+
try {
|
|
231
|
+
for (let n = 0; n < s.length; n += F)
|
|
232
|
+
await this._postFssBatch(s.slice(n, n + F), o, e);
|
|
233
|
+
i && console.info(`Wrote ${s.length} file location(s) to commands API ✅`);
|
|
234
|
+
} catch (n) {
|
|
235
|
+
const r = [...s, ...this._pendingItems];
|
|
236
|
+
throw this._pendingItems = r, await N({ spaceId: o, items: r }), n;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async _postFssBatch(t, o, e) {
|
|
240
|
+
const i = await fetch(`${this._gatewayUrl}${Q}`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
Authorization: `Bearer ${e}`,
|
|
244
|
+
"Content-Type": "application/json",
|
|
245
|
+
"x-project-id": o
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify({ items: t })
|
|
248
|
+
});
|
|
249
|
+
if (!i.ok) {
|
|
250
|
+
const s = await i.text().catch(() => "");
|
|
251
|
+
throw new Error(`File structure batch POST failed: ${i.status} ${i.statusText}${s ? ` — ${s}` : ""}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
_stopFlushTimer() {
|
|
255
|
+
this._flushTimer !== null && (clearInterval(this._flushTimer), this._flushTimer = null);
|
|
191
256
|
}
|
|
192
257
|
/**
|
|
193
258
|
* Scans `localFiles` and returns a per-extension cost breakdown.
|
|
@@ -197,44 +262,51 @@ WHERE {
|
|
|
197
262
|
* shown to the user before or after calling {@link sync}.
|
|
198
263
|
*/
|
|
199
264
|
async scanCost(t) {
|
|
200
|
-
|
|
201
|
-
const o =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
265
|
+
if ($ || ($ = k()), await $, !C) throw new Error("WASM scan function not initialised");
|
|
266
|
+
const o = 200, e = /* @__PURE__ */ new Map();
|
|
267
|
+
for (let i = 0; i < t.length; i += o) {
|
|
268
|
+
const s = t.slice(i, i + o), n = await Promise.all(
|
|
269
|
+
s.map(async (a) => ({
|
|
270
|
+
originalPath: a.relativePath,
|
|
271
|
+
data: new Uint8Array(await P(a.fullPath))
|
|
272
|
+
}))
|
|
273
|
+
), r = C(n);
|
|
274
|
+
for (const a of r) {
|
|
275
|
+
const l = e.get(a.ext);
|
|
276
|
+
l ? (l.count += a.count, l.units += a.units, l.sizeMb += a.sizeMb) : e.set(a.ext, { ...a });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return Array.from(e.values());
|
|
208
280
|
}
|
|
209
281
|
_logProgress(t, o, e, i, s) {
|
|
210
282
|
if (!s || o === 0 || t % Math.ceil(o / 100) !== 0) return;
|
|
211
|
-
const
|
|
283
|
+
const n = Math.floor(t / o * 100);
|
|
212
284
|
console.info(
|
|
213
|
-
`Progress: ${
|
|
285
|
+
`Progress: ${n}% (${t}/${o} files, ${e}/${i} bytes)`
|
|
214
286
|
);
|
|
215
287
|
}
|
|
216
288
|
}
|
|
217
|
-
const
|
|
218
|
-
class
|
|
289
|
+
const Z = "spaces_raw_eu_west6", tt = "spaces_processed_eu_west6";
|
|
290
|
+
class ut extends v {
|
|
219
291
|
constructor(t) {
|
|
220
292
|
super(t);
|
|
221
293
|
}
|
|
222
294
|
_buildApi(t) {
|
|
223
|
-
const o =
|
|
295
|
+
const o = b(this._app, Z), e = b(this._app, tt);
|
|
224
296
|
if (this._isEmulator) {
|
|
225
|
-
const
|
|
226
|
-
|
|
297
|
+
const r = this._endpoints.storageEmulatorHost, a = this._endpoints.storageEmulatorPort;
|
|
298
|
+
T(o, r, a), T(e, r, a);
|
|
227
299
|
}
|
|
228
|
-
const i = new z({ storageRaw: o, storageProcessed: e }), s = new X(this.auth, t, i, this._endpoints.gatewayUrl);
|
|
229
|
-
return
|
|
300
|
+
const i = new z({ storageRaw: o, storageProcessed: e }), s = new X(this.auth, t, i, this._endpoints.gatewayUrl), n = new O(this.auth, this._endpoints.gatewayUrl, t, s);
|
|
301
|
+
return s._bindApi(n), n;
|
|
230
302
|
}
|
|
231
303
|
}
|
|
232
304
|
export {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
305
|
+
v as Cue,
|
|
306
|
+
O as CueApi,
|
|
307
|
+
dt as CueAuth,
|
|
308
|
+
ut as CueNode,
|
|
309
|
+
_t as CueProfile,
|
|
310
|
+
gt as CueProjects,
|
|
239
311
|
X as CueSyncApi
|
|
240
312
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaecy/cue-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -14,9 +14,6 @@
|
|
|
14
14
|
"types": "./node.d.ts"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"dir-scanner-wasm": "file:../../../data/dir-scanner-wasm-0.1.0.tgz"
|
|
19
|
-
},
|
|
20
17
|
"peerDependencies": {
|
|
21
18
|
"firebase": ">=10.0.0"
|
|
22
19
|
}
|