@verentis/sdk 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,1104 @@
1
+ // src/auth/api-key.auth.ts
2
+ var ApiKeyAuth = class {
3
+ constructor(apiKey) {
4
+ this.apiKey = apiKey;
5
+ if (!apiKey.startsWith("vrt_")) {
6
+ throw new Error('Invalid API key format: must start with "vrt_"');
7
+ }
8
+ }
9
+ apiKey;
10
+ async getAuthHeader() {
11
+ return `Bearer ${this.apiKey}`;
12
+ }
13
+ };
14
+
15
+ // src/auth/bridge.auth.ts
16
+ var SKEW_MS = 3e4;
17
+ var BridgeAuth = class {
18
+ constructor(hostOrigin) {
19
+ this.hostOrigin = hostOrigin;
20
+ this._messageHandler = this.handleMessage.bind(this);
21
+ if (typeof window !== "undefined") {
22
+ window.addEventListener("message", this._messageHandler);
23
+ }
24
+ }
25
+ hostOrigin;
26
+ _token = null;
27
+ _expiresAt = 0;
28
+ _refreshTimer = null;
29
+ _pendingRefresh = null;
30
+ _messageHandler;
31
+ /** Set the initial token received from the host's `verentis:init` message */
32
+ setToken(info) {
33
+ this._token = info.accessToken;
34
+ this._expiresAt = new Date(info.expiresAt).getTime();
35
+ this.scheduleRefresh();
36
+ }
37
+ async getAuthHeader() {
38
+ if (!this._token) return null;
39
+ if (this.isExpired()) {
40
+ await this.requestRefresh();
41
+ }
42
+ return `Bearer ${this._token}`;
43
+ }
44
+ destroy() {
45
+ if (this._refreshTimer) clearTimeout(this._refreshTimer);
46
+ if (typeof window !== "undefined") {
47
+ window.removeEventListener("message", this._messageHandler);
48
+ }
49
+ }
50
+ isExpired() {
51
+ return Date.now() >= this._expiresAt - SKEW_MS;
52
+ }
53
+ _refreshResolve = null;
54
+ requestRefresh() {
55
+ if (this._pendingRefresh) return this._pendingRefresh;
56
+ const requestId = crypto.randomUUID();
57
+ this._pendingRefresh = new Promise((resolve) => {
58
+ this._refreshResolve = resolve;
59
+ });
60
+ const message = {
61
+ type: "verentis:token:refresh",
62
+ version: 1,
63
+ requestId
64
+ };
65
+ window.parent.postMessage(message, this.hostOrigin);
66
+ return this._pendingRefresh;
67
+ }
68
+ handleMessage(event) {
69
+ if (event.origin !== this.hostOrigin) return;
70
+ const data = event.data;
71
+ if (data?.type !== "verentis:token:refreshed") return;
72
+ this._token = data.token.accessToken;
73
+ this._expiresAt = new Date(data.token.expiresAt).getTime();
74
+ this._pendingRefresh = null;
75
+ this._refreshResolve?.();
76
+ this._refreshResolve = null;
77
+ this.scheduleRefresh();
78
+ }
79
+ scheduleRefresh() {
80
+ if (this._refreshTimer) clearTimeout(this._refreshTimer);
81
+ if (this._expiresAt === 0) return;
82
+ const delay2 = Math.max(0, this._expiresAt - Date.now() - SKEW_MS);
83
+ this._refreshTimer = setTimeout(() => void this.requestRefresh(), delay2);
84
+ }
85
+ };
86
+
87
+ // src/auth/token.auth.ts
88
+ var SKEW_MS2 = 3e4;
89
+ var TokenAuth = class {
90
+ _token;
91
+ _expiresAt;
92
+ _refreshTimer = null;
93
+ _refreshFn;
94
+ constructor(token, expiresAt, refreshFn) {
95
+ this._token = token;
96
+ this._expiresAt = expiresAt ? new Date(expiresAt).getTime() : Infinity;
97
+ this._refreshFn = refreshFn;
98
+ this.scheduleRefresh();
99
+ }
100
+ async getAuthHeader() {
101
+ if (this.isExpired() && this._refreshFn) {
102
+ await this.refresh();
103
+ }
104
+ return `Bearer ${this._token}`;
105
+ }
106
+ /** Update the token (e.g. after a refresh) */
107
+ update(info) {
108
+ this._token = info.accessToken;
109
+ this._expiresAt = new Date(info.expiresAt).getTime();
110
+ this.scheduleRefresh();
111
+ }
112
+ destroy() {
113
+ if (this._refreshTimer) {
114
+ clearTimeout(this._refreshTimer);
115
+ this._refreshTimer = null;
116
+ }
117
+ }
118
+ isExpired() {
119
+ return Date.now() >= this._expiresAt - SKEW_MS2;
120
+ }
121
+ async refresh() {
122
+ if (!this._refreshFn) return;
123
+ const info = await this._refreshFn();
124
+ this.update(info);
125
+ }
126
+ scheduleRefresh() {
127
+ if (this._refreshTimer) clearTimeout(this._refreshTimer);
128
+ if (!this._refreshFn || this._expiresAt === Infinity) return;
129
+ const delay2 = Math.max(0, this._expiresAt - Date.now() - SKEW_MS2);
130
+ this._refreshTimer = setTimeout(() => void this.refresh(), delay2);
131
+ }
132
+ };
133
+
134
+ // src/modules/context.ts
135
+ var ContextModule = class {
136
+ _workspace = null;
137
+ _surface = null;
138
+ _file = null;
139
+ _entryPoint = null;
140
+ _apiUrl = null;
141
+ /** The current workspace */
142
+ get workspace() {
143
+ if (!this._workspace) {
144
+ throw new Error("Workspace context not available. Ensure the client is initialized.");
145
+ }
146
+ return this._workspace;
147
+ }
148
+ /** The host surface that launched the app */
149
+ get surface() {
150
+ if (!this._surface) {
151
+ throw new Error("Launch surface not available. Ensure the client is initialized.");
152
+ }
153
+ return this._surface;
154
+ }
155
+ /** The file that triggered the app to open on a file surface */
156
+ get file() {
157
+ if (!this._file) {
158
+ throw new Error("File context not available. The app may be running on a workspace surface.");
159
+ }
160
+ return this._file;
161
+ }
162
+ /** The API URL */
163
+ get apiUrl() {
164
+ if (!this._apiUrl) {
165
+ throw new Error("API URL not available. Ensure the client is initialized.");
166
+ }
167
+ return this._apiUrl;
168
+ }
169
+ /** Whether a file context is available */
170
+ get hasFile() {
171
+ return this._file !== null;
172
+ }
173
+ /** The application entry point that matched the opened file */
174
+ get entryPoint() {
175
+ return this._entryPoint;
176
+ }
177
+ /** Whether an entry point context is available */
178
+ get hasEntryPoint() {
179
+ return this._entryPoint !== null;
180
+ }
181
+ /** Update context from the host's init message */
182
+ setFromAppContext(ctx) {
183
+ this._workspace = ctx.workspace;
184
+ this._surface = ctx.surface ?? "file";
185
+ this._file = ctx.file ?? null;
186
+ this._entryPoint = ctx.entryPoint ?? null;
187
+ this._apiUrl = ctx.apiUrl;
188
+ }
189
+ /** Set workspace info directly (standalone mode) */
190
+ setWorkspace(workspace) {
191
+ this._workspace = {
192
+ id: workspace.id,
193
+ slug: workspace.slug ?? "",
194
+ name: workspace.name ?? ""
195
+ };
196
+ }
197
+ /** Set API URL directly */
198
+ setApiUrl(url) {
199
+ this._apiUrl = url;
200
+ }
201
+ };
202
+
203
+ // src/modules/executions.ts
204
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
205
+ "Completed",
206
+ "Failed",
207
+ "Cancelled",
208
+ "TimedOut"
209
+ ]);
210
+ function isTerminalStatus(status) {
211
+ return TERMINAL_STATUSES.has(status);
212
+ }
213
+ var ExecutionsModule = class {
214
+ constructor(http, workspaceId) {
215
+ this.http = http;
216
+ this.workspaceId = workspaceId;
217
+ }
218
+ http;
219
+ workspaceId;
220
+ /**
221
+ * Run a file in the virtual file system. The platform resolves the appropriate engine from the
222
+ * file's type (e.g. a `.py` file runs on the Python engine).
223
+ */
224
+ async run(filePath, options) {
225
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
226
+ return this.http.request("POST", "/executions", {
227
+ body: JSON.stringify({
228
+ workspaceId,
229
+ filePath,
230
+ branch: options?.branch,
231
+ tool: options?.tool,
232
+ mode: options?.mode,
233
+ arguments: options?.arguments ?? [],
234
+ parameters: serializeParameters(options?.parameters)
235
+ }),
236
+ headers: { "Content-Type": "application/json" }
237
+ });
238
+ }
239
+ /**
240
+ * Execute a specific engine by id, optionally against a file and/or with structured parameters
241
+ * (e.g. invoking an engine tool without a script).
242
+ */
243
+ async execute(engineId, options) {
244
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
245
+ return this.http.request("POST", "/executions", {
246
+ body: JSON.stringify({
247
+ workspaceId,
248
+ engineId,
249
+ filePath: options?.filePath,
250
+ branch: options?.branch,
251
+ tool: options?.tool,
252
+ mode: options?.mode,
253
+ arguments: options?.arguments ?? [],
254
+ parameters: serializeParameters(options?.parameters)
255
+ }),
256
+ headers: { "Content-Type": "application/json" }
257
+ });
258
+ }
259
+ /** Get an execution by id. */
260
+ async get(id, options) {
261
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
262
+ return this.http.request("GET", `/executions/${encodeURIComponent(id)}`, {
263
+ params: { workspaceId }
264
+ });
265
+ }
266
+ /** List workspace executions with optional engine, file, lifecycle, and time filters. */
267
+ async list(options) {
268
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
269
+ return this.http.request("GET", "/executions", {
270
+ params: {
271
+ workspaceId,
272
+ filePath: options?.filePath,
273
+ engineIds: options?.engineIds,
274
+ filePaths: options?.filePaths,
275
+ status: options?.status,
276
+ trigger: options?.trigger,
277
+ activeOnly: options?.activeOnly,
278
+ requestedAfter: options?.requestedAfter,
279
+ requestedBefore: options?.requestedBefore,
280
+ pageNo: options?.pageNo ?? 1,
281
+ pageSize: options?.pageSize ?? 20,
282
+ orderBy: options?.orderBy
283
+ }
284
+ });
285
+ }
286
+ /** Get the captured stdout/stderr of an execution. */
287
+ async logs(id, options) {
288
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
289
+ return this.http.request("GET", `/executions/${encodeURIComponent(id)}/logs`, {
290
+ params: { workspaceId }
291
+ });
292
+ }
293
+ /** Cancel a pending or running execution. */
294
+ async cancel(id, options) {
295
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
296
+ return this.http.request("POST", `/executions/${encodeURIComponent(id)}/cancel`, {
297
+ body: JSON.stringify({ id, workspaceId }),
298
+ headers: { "Content-Type": "application/json" }
299
+ });
300
+ }
301
+ /**
302
+ * Poll an execution until it reaches a terminal status. Returns immediately if it is already
303
+ * terminal. Throws if `timeoutMs` elapses or the supplied signal aborts.
304
+ */
305
+ async wait(id, options) {
306
+ const interval = options?.intervalMs ?? 1e3;
307
+ const deadline = options?.timeoutMs ? Date.now() + options.timeoutMs : void 0;
308
+ for (; ; ) {
309
+ const execution = await this.get(id, { workspaceId: options?.workspaceId });
310
+ if (isTerminalStatus(execution.status)) {
311
+ return execution;
312
+ }
313
+ if (deadline !== void 0 && Date.now() >= deadline) {
314
+ throw new Error(`Timed out waiting for execution '${id}' to complete`);
315
+ }
316
+ await delay(interval, options?.signal);
317
+ }
318
+ }
319
+ /**
320
+ * Run a file and resolve once it has completed. For synchronous (request-response) executions the
321
+ * initial response is already terminal; otherwise it polls via {@link wait}.
322
+ */
323
+ async runAndWait(filePath, options) {
324
+ const execution = await this.run(filePath, options);
325
+ if (isTerminalStatus(execution.status)) {
326
+ return execution;
327
+ }
328
+ return this.wait(execution.id, options);
329
+ }
330
+ requireWorkspaceId(override) {
331
+ const id = override ?? this.workspaceId;
332
+ if (!id) {
333
+ throw new Error("workspaceId is required: provide it in options or when creating the client");
334
+ }
335
+ return id;
336
+ }
337
+ };
338
+ function serializeParameters(parameters) {
339
+ return parameters === void 0 ? void 0 : JSON.stringify(parameters);
340
+ }
341
+ function delay(ms, signal) {
342
+ if (signal?.aborted) {
343
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
344
+ }
345
+ return new Promise((resolve, reject) => {
346
+ const timer = setTimeout(resolve, ms);
347
+ signal?.addEventListener(
348
+ "abort",
349
+ () => {
350
+ clearTimeout(timer);
351
+ reject(new DOMException("Aborted", "AbortError"));
352
+ },
353
+ { once: true }
354
+ );
355
+ });
356
+ }
357
+
358
+ // src/modules/engines.ts
359
+ var EnginesModule = class {
360
+ constructor(http, workspaceId) {
361
+ this.http = http;
362
+ this.workspaceId = workspaceId;
363
+ }
364
+ http;
365
+ workspaceId;
366
+ async list(options) {
367
+ return this.http.request("GET", "/engines", {
368
+ params: {
369
+ workspaceId: this.requireWorkspaceId(options?.workspaceId),
370
+ pageNo: options?.pageNo ?? 1,
371
+ pageSize: options?.pageSize ?? 20,
372
+ orderBy: options?.orderBy
373
+ }
374
+ });
375
+ }
376
+ async get(id, options) {
377
+ return this.http.request("GET", `/engines/${encodeURIComponent(id)}`, {
378
+ params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
379
+ });
380
+ }
381
+ requireWorkspaceId(override) {
382
+ const workspaceId = override ?? this.workspaceId;
383
+ if (!workspaceId) {
384
+ throw new Error("workspaceId is required: provide it in options or when creating the client");
385
+ }
386
+ return workspaceId;
387
+ }
388
+ };
389
+
390
+ // src/modules/files.ts
391
+ var FilesModule = class {
392
+ constructor(http, workspaceId) {
393
+ this.http = http;
394
+ this.workspaceId = workspaceId;
395
+ }
396
+ http;
397
+ workspaceId;
398
+ /**
399
+ * Get file metadata or directory listing.
400
+ * For files: returns metadata (path, name, mimeType, size, etc.).
401
+ * For directories: returns metadata with children array populated.
402
+ */
403
+ async get(path, options) {
404
+ const normalizedPath = normalizePath(path);
405
+ return this.http.request("GET", `/files/${normalizedPath}`, {
406
+ params: {
407
+ branch: options?.branch ?? "main",
408
+ pageNo: options?.pageNo ?? 1,
409
+ pageSize: options?.pageSize ?? 20,
410
+ orderBy: options?.orderBy
411
+ },
412
+ headers: this.workspaceHeaders()
413
+ });
414
+ }
415
+ /**
416
+ * Read the raw content of a file as a Blob.
417
+ */
418
+ async readContent(path, branch) {
419
+ const normalizedPath = normalizePath(path);
420
+ const response = await this.http.requestRaw("GET", `/files/raw/${normalizedPath}`, {
421
+ params: { branch: branch ?? "main" },
422
+ headers: {
423
+ ...this.workspaceHeaders(),
424
+ Accept: "*/*"
425
+ }
426
+ });
427
+ if (!response.ok) {
428
+ throw new Error(`Failed to read file content: ${response.status} ${response.statusText}`);
429
+ }
430
+ return response.blob();
431
+ }
432
+ /** Read UTF-8 text content from a workspace file. */
433
+ async readText(path, branch) {
434
+ return (await this.readContent(path, branch)).text();
435
+ }
436
+ /** Inspect an extracted or sealed package node without exposing sealed payload content. */
437
+ async getPackageDescriptor(path, branch = "main") {
438
+ return this.http.request("GET", "/files/package-descriptor", {
439
+ params: { branch, path },
440
+ headers: this.workspaceHeaders()
441
+ });
442
+ }
443
+ /**
444
+ * Download a file or directory as an archive (zip or tgz).
445
+ * Only applicable when the target has children or you want a packaged download.
446
+ */
447
+ async readArchive(path, options) {
448
+ const normalizedPath = normalizePath(path);
449
+ const format = options?.format ?? "zip";
450
+ const response = await this.http.requestRaw("GET", `/files/raw/${normalizedPath}`, {
451
+ params: {
452
+ branch: options?.branch ?? "main",
453
+ format
454
+ },
455
+ headers: {
456
+ ...this.workspaceHeaders(),
457
+ Accept: format === "tgz" ? "application/gzip" : "application/zip"
458
+ }
459
+ });
460
+ if (!response.ok) {
461
+ throw new Error(`Failed to download archive: ${response.status} ${response.statusText}`);
462
+ }
463
+ return response.blob();
464
+ }
465
+ /**
466
+ * Upload an archive (zip or tgz) and auto-extract its contents as individual nodes.
467
+ * If the archive contains a `.verentis-manifest.json`, the original metadata
468
+ * (display names, mime types, parent-content relationships) will be honoured.
469
+ */
470
+ async extractUpload(path, file, options) {
471
+ const normalizedPath = normalizePath(path);
472
+ const formData = new FormData();
473
+ const filename = options?.filename ?? "archive.zip";
474
+ formData.append("file", file, filename);
475
+ const metadata = {};
476
+ if (options?.branch) metadata.branch = options.branch;
477
+ formData.append("metadata", JSON.stringify(metadata));
478
+ await this.http.request("POST", `/files/${normalizedPath}`, {
479
+ body: formData,
480
+ headers: {
481
+ ...this.workspaceHeaders(),
482
+ "X-Auto-Extract": "true"
483
+ }
484
+ });
485
+ }
486
+ /**
487
+ * Upload or create a file.
488
+ */
489
+ async write(path, options) {
490
+ const normalizedPath = normalizePath(path);
491
+ const formData = new FormData();
492
+ let blob;
493
+ if (typeof options.content === "string") {
494
+ blob = new Blob([options.content], { type: options.contentType ?? "text/plain" });
495
+ } else if (options.content instanceof ArrayBuffer) {
496
+ blob = new Blob([options.content], { type: options.contentType ?? "application/octet-stream" });
497
+ } else {
498
+ blob = options.content;
499
+ }
500
+ const filename = options.filename ?? extractFilename(path);
501
+ formData.append("file", blob, filename);
502
+ const metadata = {};
503
+ if (options.contentType) metadata.contentType = options.contentType;
504
+ if (options.branch) metadata.branch = options.branch;
505
+ formData.append("metadata", JSON.stringify(metadata));
506
+ await this.http.request("POST", `/files/${normalizedPath}`, {
507
+ body: formData,
508
+ headers: this.workspaceHeaders()
509
+ });
510
+ }
511
+ /**
512
+ * Move or rename a file.
513
+ */
514
+ async move(options) {
515
+ await this.http.request("PUT", "/files", {
516
+ body: JSON.stringify({
517
+ from: normalizePath(options.from),
518
+ to: normalizePath(options.to),
519
+ branch: options.branch
520
+ }),
521
+ headers: {
522
+ ...this.workspaceHeaders(),
523
+ "Content-Type": "application/json"
524
+ }
525
+ });
526
+ }
527
+ /**
528
+ * Delete a file.
529
+ */
530
+ async remove(options) {
531
+ await this.http.request("DELETE", "/files", {
532
+ body: JSON.stringify({
533
+ path: normalizePath(options.path),
534
+ branch: options.branch
535
+ }),
536
+ headers: {
537
+ ...this.workspaceHeaders(),
538
+ "Content-Type": "application/json"
539
+ }
540
+ });
541
+ }
542
+ workspaceHeaders() {
543
+ const headers = {};
544
+ if (this.workspaceId) {
545
+ headers["X-Workspace-Id"] = this.workspaceId;
546
+ }
547
+ return headers;
548
+ }
549
+ };
550
+ function normalizePath(path) {
551
+ return path.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
552
+ }
553
+ function extractFilename(path) {
554
+ const segments = path.split("/");
555
+ return segments[segments.length - 1] || "file";
556
+ }
557
+
558
+ // src/modules/installations.ts
559
+ var InstallationsModule = class {
560
+ constructor(http, workspaceId) {
561
+ this.http = http;
562
+ this.workspaceId = workspaceId;
563
+ }
564
+ http;
565
+ workspaceId;
566
+ async list(options) {
567
+ return this.http.request("GET", "/installations", {
568
+ params: {
569
+ workspaceId: this.requireWorkspaceId(options?.workspaceId),
570
+ pageNo: options?.pageNo ?? 1,
571
+ pageSize: options?.pageSize ?? 20,
572
+ orderBy: options?.orderBy,
573
+ packageId: options?.packageId,
574
+ branch: options?.branch,
575
+ installPath: options?.installPath,
576
+ status: options?.status,
577
+ isSystem: options?.isSystem
578
+ }
579
+ });
580
+ }
581
+ async resolve(installPath, options) {
582
+ return this.http.request("GET", "/installations/resolve", {
583
+ params: {
584
+ workspaceId: this.requireWorkspaceId(options?.workspaceId),
585
+ branch: options?.branch ?? "main",
586
+ installPath
587
+ }
588
+ });
589
+ }
590
+ async update(id, options) {
591
+ const workspaceId = this.requireWorkspaceId(options?.workspaceId);
592
+ return this.http.request(
593
+ "PUT",
594
+ `/installations/${encodeURIComponent(id)}`,
595
+ {
596
+ body: JSON.stringify({
597
+ id,
598
+ workspaceId,
599
+ packageVersion: options?.packageVersion,
600
+ installMode: options?.installMode
601
+ }),
602
+ headers: { "Content-Type": "application/json" }
603
+ }
604
+ );
605
+ }
606
+ async uninstall(id, options) {
607
+ await this.http.request("DELETE", `/installations/${encodeURIComponent(id)}`, {
608
+ params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
609
+ });
610
+ }
611
+ requireWorkspaceId(override) {
612
+ const workspaceId = override ?? this.workspaceId;
613
+ if (!workspaceId) {
614
+ throw new Error("workspaceId is required: provide it in options or when creating the client");
615
+ }
616
+ return workspaceId;
617
+ }
618
+ };
619
+
620
+ // src/modules/resources.ts
621
+ var ResourcesModule = class {
622
+ constructor(http, workspaceId) {
623
+ this.http = http;
624
+ this.workspaceId = workspaceId;
625
+ }
626
+ http;
627
+ workspaceId;
628
+ async list(options) {
629
+ return this.http.request("GET", "/resources", {
630
+ params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
631
+ });
632
+ }
633
+ async get(id, options) {
634
+ return this.http.request(
635
+ "GET",
636
+ `/resources/${encodeURIComponent(id)}`,
637
+ { params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) } }
638
+ );
639
+ }
640
+ requireWorkspaceId(override) {
641
+ const workspaceId = override ?? this.workspaceId;
642
+ if (!workspaceId) {
643
+ throw new Error("workspaceId is required: provide it in options or when creating the client");
644
+ }
645
+ return workspaceId;
646
+ }
647
+ };
648
+
649
+ // src/modules/settings.ts
650
+ var SettingsModule = class {
651
+ constructor(http, workspaceId) {
652
+ this.http = http;
653
+ this.workspaceId = workspaceId;
654
+ }
655
+ http;
656
+ workspaceId;
657
+ /** List every setting group visible to the workspace (definitions merged with values). */
658
+ async list(options) {
659
+ return this.http.request("GET", "/settings", {
660
+ params: this.scopeParams(options)
661
+ });
662
+ }
663
+ /** Get a single setting by its full key (e.g. `graphite.api-key`). */
664
+ async get(key, options) {
665
+ return this.http.request("GET", `/settings/${encodeURIComponent(key)}`, {
666
+ params: this.scopeParams(options)
667
+ });
668
+ }
669
+ /** Get a setting's value by its full key, or `defaultValue` when unset. */
670
+ async getValue(key, defaultValue, options) {
671
+ const item = await this.get(key, options);
672
+ return item.value ?? item.defaultValue ?? defaultValue;
673
+ }
674
+ /**
675
+ * Check exact setting keys for retained stored values and effective defaults.
676
+ * This never returns value content and also finds values retained after a definition is removed.
677
+ */
678
+ async presence(keys, options) {
679
+ if (keys.length === 0) return [];
680
+ return this.http.request("GET", "/settings/presence", {
681
+ params: {
682
+ scopeKind: "Workspace",
683
+ parentId: this.requireWorkspaceId(options?.workspaceId),
684
+ keys
685
+ }
686
+ });
687
+ }
688
+ /** Upsert a setting value by its full key. Requires the `fabric.setting.update` scope. */
689
+ async set(key, value, options) {
690
+ await this.http.request("PUT", `/settings/${encodeURIComponent(key)}`, {
691
+ params: {
692
+ scopeKind: "Workspace",
693
+ parentId: this.requireWorkspaceId(options?.workspaceId)
694
+ },
695
+ body: JSON.stringify(value ?? null),
696
+ headers: { "Content-Type": "application/json" }
697
+ });
698
+ }
699
+ /** Delete a stored value (reverting to the definition default). Requires `fabric.setting.delete`. */
700
+ async reset(key, options) {
701
+ await this.http.request("DELETE", `/settings/${encodeURIComponent(key)}`, {
702
+ params: {
703
+ scopeKind: "Workspace",
704
+ parentId: this.requireWorkspaceId(options?.workspaceId)
705
+ }
706
+ });
707
+ }
708
+ /**
709
+ * A view of this module where every key is auto-prefixed with `{namespace}.` — the platform's
710
+ * manifest namespacing convention. An app named `graphite` declaring `key: api-key` reads it as:
711
+ *
712
+ * ```ts
713
+ * const settings = client.settings.namespaced('graphite')
714
+ * const apiKey = await settings.getValue<string>('api-key')
715
+ * ```
716
+ */
717
+ namespaced(namespace) {
718
+ const prefix = namespace.endsWith(".") ? namespace : `${namespace}.`;
719
+ return {
720
+ get: (key, options) => this.get(prefix + key, options),
721
+ getValue: (key, defaultValue, options) => this.getValue(prefix + key, defaultValue, options),
722
+ set: (key, value, options) => this.set(prefix + key, value, options),
723
+ reset: (key, options) => this.reset(prefix + key, options)
724
+ };
725
+ }
726
+ scopeParams(options) {
727
+ return {
728
+ scopeKind: "Workspace",
729
+ parentId: this.requireWorkspaceId(options?.workspaceId),
730
+ // Secret values never reach browser callers; the platform rejects `true` for
731
+ // anything but trusted service-to-service identities.
732
+ includeSecretValues: "false"
733
+ };
734
+ }
735
+ requireWorkspaceId(override) {
736
+ const workspaceId = override ?? this.workspaceId;
737
+ if (!workspaceId) {
738
+ throw new Error(
739
+ "workspaceId is required: pass it in options or configure the client with one"
740
+ );
741
+ }
742
+ return workspaceId;
743
+ }
744
+ };
745
+
746
+ // src/transport/bridge.transport.ts
747
+ var Bridge = class {
748
+ listeners = /* @__PURE__ */ new Map();
749
+ _initResolve;
750
+ _initPromise;
751
+ _hostOrigin = null;
752
+ _messageHandler;
753
+ _destroyed = false;
754
+ constructor() {
755
+ this._initPromise = new Promise((resolve) => {
756
+ this._initResolve = resolve;
757
+ });
758
+ this._messageHandler = this.handleMessage.bind(this);
759
+ if (typeof window !== "undefined") {
760
+ window.addEventListener("message", this._messageHandler);
761
+ }
762
+ }
763
+ /** Post `verentis:ready` to the host to start the handshake */
764
+ sendReady(capabilities) {
765
+ console.info("[Verentis SDK] Sending verentis:ready to host");
766
+ const message = {
767
+ type: "verentis:ready",
768
+ version: 1,
769
+ capabilities
770
+ };
771
+ window.parent.postMessage(message, "*");
772
+ }
773
+ /** Wait for the host to send `verentis:init` with context and token */
774
+ waitForInit() {
775
+ return this._initPromise;
776
+ }
777
+ /** Get the host origin (available after init) */
778
+ get hostOrigin() {
779
+ return this._hostOrigin;
780
+ }
781
+ /** Send a message to the host */
782
+ postToHost(message) {
783
+ if (!this._hostOrigin) {
784
+ throw new Error("Bridge not initialized: host origin unknown. Wait for init first.");
785
+ }
786
+ window.parent.postMessage(message, this._hostOrigin);
787
+ }
788
+ /**
789
+ * Navigate to another location in the workspace.
790
+ *
791
+ * @param path A workspace file path (e.g. `/reports/q1.json`) or an absolute
792
+ * http(s) URL. External URLs require the app to declare the
793
+ * `navigation:external` capability in its manifest.
794
+ * @param options Navigation options. Set `newTab: true` to open the target in
795
+ * a new browser tab instead of the current one.
796
+ */
797
+ navigate(path, options) {
798
+ this.postToHost({
799
+ type: "verentis:navigate",
800
+ version: 1,
801
+ path,
802
+ ...options?.newTab ? { newTab: true } : {}
803
+ });
804
+ }
805
+ /** Request iframe resize */
806
+ resize(height) {
807
+ this.postToHost({ type: "verentis:resize", version: 1, height });
808
+ }
809
+ /** Update the title displayed by the host */
810
+ setTitle(title) {
811
+ this.postToHost({ type: "verentis:title", version: 1, title });
812
+ }
813
+ /** Signal unsaved changes to the host */
814
+ setDirty(isDirty) {
815
+ this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
816
+ }
817
+ /** Subscribe to a specific message type from the host */
818
+ on(type, listener) {
819
+ if (!this.listeners.has(type)) {
820
+ this.listeners.set(type, /* @__PURE__ */ new Set());
821
+ }
822
+ this.listeners.get(type).add(listener);
823
+ return () => {
824
+ this.listeners.get(type)?.delete(listener);
825
+ };
826
+ }
827
+ /** Clean up event listeners */
828
+ destroy() {
829
+ this._destroyed = true;
830
+ this.listeners.clear();
831
+ if (typeof window !== "undefined") {
832
+ window.removeEventListener("message", this._messageHandler);
833
+ }
834
+ }
835
+ handleMessage(event) {
836
+ if (this._destroyed) return;
837
+ const data = event.data;
838
+ if (!data?.type?.startsWith("verentis:")) return;
839
+ if (data.type === "verentis:init") {
840
+ this._hostOrigin = event.origin;
841
+ console.info("[Verentis SDK] Received verentis:init from host", {
842
+ origin: event.origin,
843
+ workspaceId: data.context.workspace.id,
844
+ filePath: data.context.file?.path ?? null
845
+ });
846
+ const initData = data;
847
+ this._initResolve({ context: initData.context, token: initData.token });
848
+ }
849
+ const typeListeners = this.listeners.get(data.type);
850
+ if (typeListeners) {
851
+ for (const listener of typeListeners) {
852
+ listener(data);
853
+ }
854
+ }
855
+ }
856
+ };
857
+
858
+ // src/types.ts
859
+ var ApiError = class extends Error {
860
+ constructor(status, detail, title) {
861
+ super(title ?? `API error ${status}: ${detail}`);
862
+ this.status = status;
863
+ this.detail = detail;
864
+ this.title = title;
865
+ this.name = "ApiError";
866
+ }
867
+ status;
868
+ detail;
869
+ title;
870
+ };
871
+
872
+ // src/transport/http.transport.ts
873
+ var HttpTransport = class {
874
+ constructor(baseUrl, auth, apiVersion = "v1") {
875
+ this.baseUrl = baseUrl;
876
+ this.auth = auth;
877
+ this.apiVersion = apiVersion;
878
+ }
879
+ baseUrl;
880
+ auth;
881
+ apiVersion;
882
+ async request(method, path, options) {
883
+ const url = this.buildUrl(path, options?.params);
884
+ console.info("[Verentis SDK] Request started", { method, path, url });
885
+ const headers = {};
886
+ const authHeader = await this.auth.getAuthHeader();
887
+ if (authHeader) {
888
+ headers["Authorization"] = authHeader;
889
+ }
890
+ if (options?.headers) {
891
+ Object.assign(headers, options.headers);
892
+ }
893
+ const response = await fetch(url, {
894
+ method,
895
+ headers,
896
+ body: options?.body
897
+ });
898
+ if (!response.ok) {
899
+ let detail = "";
900
+ let title;
901
+ try {
902
+ const body = await response.json();
903
+ detail = body.detail ?? body.message ?? JSON.stringify(body);
904
+ title = body.title;
905
+ } catch {
906
+ detail = await response.text().catch(() => response.statusText);
907
+ }
908
+ console.error("[Verentis SDK] Request failed", {
909
+ method,
910
+ path,
911
+ status: response.status,
912
+ detail
913
+ });
914
+ throw new ApiError(response.status, detail, title);
915
+ }
916
+ console.info("[Verentis SDK] Request succeeded", {
917
+ method,
918
+ path,
919
+ status: response.status,
920
+ contentType: response.headers.get("content-type") ?? ""
921
+ });
922
+ const contentType = response.headers.get("content-type") ?? "";
923
+ if (contentType.includes("application/json")) {
924
+ return response.json();
925
+ }
926
+ return response.blob();
927
+ }
928
+ async requestRaw(method, path, options) {
929
+ const url = this.buildUrl(path, options?.params);
930
+ console.info("[Verentis SDK] Raw request started", { method, path, url });
931
+ const headers = {};
932
+ const authHeader = await this.auth.getAuthHeader();
933
+ if (authHeader) {
934
+ headers["Authorization"] = authHeader;
935
+ }
936
+ if (options?.headers) {
937
+ Object.assign(headers, options.headers);
938
+ }
939
+ const response = await fetch(url, { method, headers, body: options?.body });
940
+ console.info("[Verentis SDK] Raw request completed", {
941
+ method,
942
+ path,
943
+ status: response.status,
944
+ ok: response.ok,
945
+ contentType: response.headers.get("content-type") ?? ""
946
+ });
947
+ return response;
948
+ }
949
+ buildUrl(path, params) {
950
+ const base = `${this.baseUrl}/${this.apiVersion}${path}`;
951
+ if (!params) return base;
952
+ const searchParams = new URLSearchParams();
953
+ for (const [key, value] of Object.entries(params)) {
954
+ if (value === void 0) {
955
+ continue;
956
+ }
957
+ if (Array.isArray(value)) {
958
+ for (const item of value) {
959
+ searchParams.append(key, String(item));
960
+ }
961
+ } else {
962
+ searchParams.set(key, String(value));
963
+ }
964
+ }
965
+ const qs = searchParams.toString();
966
+ return qs ? `${base}?${qs}` : base;
967
+ }
968
+ };
969
+
970
+ // src/client.ts
971
+ var VerentisClient = class {
972
+ files;
973
+ executions;
974
+ engines;
975
+ installations;
976
+ resources;
977
+ settings;
978
+ context;
979
+ bridge;
980
+ _auth;
981
+ _http;
982
+ _ready;
983
+ constructor(options) {
984
+ this.context = new ContextModule();
985
+ const environment = detectEnvironment();
986
+ if (environment === "iframe" && !options?.auth) {
987
+ this.bridge = new Bridge();
988
+ const bridgeAuth = new BridgeAuth("*");
989
+ this._auth = bridgeAuth;
990
+ this._ready = this.initFromBridge(bridgeAuth);
991
+ } else {
992
+ this.bridge = null;
993
+ this._auth = createAuthStrategy(options);
994
+ const apiUrl = options?.apiUrl;
995
+ if (!apiUrl) {
996
+ throw new Error("apiUrl is required in standalone mode");
997
+ }
998
+ this.context.setApiUrl(apiUrl);
999
+ if (options?.workspaceId) {
1000
+ this.context.setWorkspace({ id: options.workspaceId });
1001
+ }
1002
+ this._http = new HttpTransport(apiUrl, this._auth, options?.apiVersion ?? "v1");
1003
+ this.files = new FilesModule(this._http, options?.workspaceId);
1004
+ this.executions = new ExecutionsModule(this._http, options?.workspaceId);
1005
+ this.engines = new EnginesModule(this._http, options?.workspaceId);
1006
+ this.installations = new InstallationsModule(this._http, options?.workspaceId);
1007
+ this.resources = new ResourcesModule(this._http, options?.workspaceId);
1008
+ this.settings = new SettingsModule(this._http, options?.workspaceId);
1009
+ this._ready = Promise.resolve();
1010
+ return;
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Returns a promise that resolves when the client is fully initialized.
1015
+ * In standalone mode this resolves immediately.
1016
+ * In iframe mode this resolves after the host sends `verentis:init`.
1017
+ */
1018
+ whenReady() {
1019
+ return this._ready;
1020
+ }
1021
+ /** Clean up resources (timers, event listeners) */
1022
+ destroy() {
1023
+ this._auth.destroy?.();
1024
+ this.bridge?.destroy();
1025
+ }
1026
+ async initFromBridge(bridgeAuth) {
1027
+ this.bridge.sendReady();
1028
+ const { context, token } = await this.bridge.waitForInit();
1029
+ const hostOrigin = this.bridge.hostOrigin;
1030
+ Object.assign(bridgeAuth, new BridgeAuth(hostOrigin));
1031
+ bridgeAuth.setToken(token);
1032
+ this.context.setFromAppContext(context);
1033
+ const http = new HttpTransport(
1034
+ context.apiUrl,
1035
+ bridgeAuth,
1036
+ "v1"
1037
+ );
1038
+ this._http = http;
1039
+ this.files = new FilesModule(http, context.workspace.id);
1040
+ this.executions = new ExecutionsModule(http, context.workspace.id);
1041
+ this.engines = new EnginesModule(http, context.workspace.id);
1042
+ this.installations = new InstallationsModule(http, context.workspace.id);
1043
+ this.resources = new ResourcesModule(http, context.workspace.id);
1044
+ this.settings = new SettingsModule(http, context.workspace.id);
1045
+ }
1046
+ };
1047
+ function detectEnvironment() {
1048
+ if (typeof window !== "undefined" && window.parent !== window) {
1049
+ return "iframe";
1050
+ }
1051
+ return "standalone";
1052
+ }
1053
+ function createAuthStrategy(options) {
1054
+ if (!options?.auth) {
1055
+ throw new Error("auth is required in standalone mode: provide { apiKey } or { token }");
1056
+ }
1057
+ if ("apiKey" in options.auth) {
1058
+ return new ApiKeyAuth(options.auth.apiKey);
1059
+ }
1060
+ if ("token" in options.auth) {
1061
+ return new TokenAuth(
1062
+ options.auth.token,
1063
+ options.auth.expiresAt,
1064
+ options.auth.refreshToken
1065
+ );
1066
+ }
1067
+ throw new Error("Invalid auth options: provide { apiKey } or { token }");
1068
+ }
1069
+
1070
+ // src/utils/mime.ts
1071
+ function matchMimeType(pattern, mimeType) {
1072
+ const normalizedPattern = stripParams(pattern).toLowerCase();
1073
+ const normalizedMime = stripParams(mimeType).toLowerCase();
1074
+ if (normalizedPattern === "*/*") return true;
1075
+ const [patternType, patternSubtype] = normalizedPattern.split("/");
1076
+ const [mimeTypeType, mimeSubtype] = normalizedMime.split("/");
1077
+ if (!patternType || !mimeTypeType) return false;
1078
+ if (patternType === "*") return true;
1079
+ if (patternType !== mimeTypeType) return false;
1080
+ if (!patternSubtype || patternSubtype === "*") return true;
1081
+ return patternSubtype === mimeSubtype;
1082
+ }
1083
+ function findBestMatch(mimeType, bindings) {
1084
+ const matches = bindings.filter((b) => matchMimeType(b.pattern, mimeType)).sort((a, b) => {
1085
+ const aExact = !a.pattern.includes("*");
1086
+ const bExact = !b.pattern.includes("*");
1087
+ if (aExact !== bExact) return aExact ? -1 : 1;
1088
+ return b.priority - a.priority;
1089
+ });
1090
+ return matches[0] ?? null;
1091
+ }
1092
+ function stripParams(mime) {
1093
+ const idx = mime.indexOf(";");
1094
+ return idx === -1 ? mime.trim() : mime.slice(0, idx).trim();
1095
+ }
1096
+
1097
+ // src/index.ts
1098
+ function createVerentisClient(options) {
1099
+ return new VerentisClient(options);
1100
+ }
1101
+
1102
+ export { ApiKeyAuth, Bridge, BridgeAuth, ContextModule, EnginesModule, ExecutionsModule, FilesModule, HttpTransport, InstallationsModule, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, findBestMatch, isTerminalStatus, matchMimeType };
1103
+ //# sourceMappingURL=index.js.map
1104
+ //# sourceMappingURL=index.js.map