@centia-io/sdk 0.2.14 → 0.2.16

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.
@@ -712,6 +712,52 @@
712
712
  getHeader: (name) => response.headers.get(name)
713
713
  };
714
714
  }
715
+ /**
716
+ * Execute a request and return the raw Response without consuming the body.
717
+ * For binary endpoints (file downloads): follows redirects (a presigned
718
+ * storage URL — the fetch spec strips Authorization on cross-origin
719
+ * redirects), sends no Content-Type, and supports extra request headers
720
+ * such as Range. Throws CentiaApiError on a status not in expectedStatus.
721
+ */
722
+ async requestRaw(opts) {
723
+ var _this3 = this;
724
+ var _opts$accept, _opts$expectedStatus2;
725
+ const url = _this3.buildUrl(opts.path, opts.query);
726
+ const headers = { "Accept": (_opts$accept = opts.accept) !== null && _opts$accept !== void 0 ? _opts$accept : "*/*" };
727
+ if (_this3.auth.getAccessToken) {
728
+ const token = await _this3.auth.getAccessToken();
729
+ if (token) headers["Authorization"] = `Bearer ${token}`;
730
+ }
731
+ if (_this3.auth.getHeaders) Object.assign(headers, await _this3.auth.getHeaders());
732
+ if (_this3.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this3.userAgent;
733
+ Object.assign(headers, opts.headers);
734
+ const response = await _this3.fetchFn(url, {
735
+ method: opts.method,
736
+ headers,
737
+ redirect: "follow"
738
+ });
739
+ if (!((_opts$expectedStatus2 = opts.expectedStatus) !== null && _opts$expectedStatus2 !== void 0 ? _opts$expectedStatus2 : [200]).includes(response.status)) {
740
+ var _ref, _parsed$message, _response$headers$get;
741
+ let bodyText = "";
742
+ try {
743
+ bodyText = await response.text();
744
+ } catch (_unused) {}
745
+ let parsed = null;
746
+ if (bodyText) try {
747
+ parsed = JSON.parse(bodyText);
748
+ } catch (_unused2) {}
749
+ throw new CentiaApiError({
750
+ message: ((_ref = (_parsed$message = parsed === null || parsed === void 0 ? void 0 : parsed.message) !== null && _parsed$message !== void 0 ? _parsed$message : parsed === null || parsed === void 0 ? void 0 : parsed.error) !== null && _ref !== void 0 ? _ref : bodyText) || `Unexpected status ${response.status}`,
751
+ status: response.status,
752
+ code: parsed === null || parsed === void 0 ? void 0 : parsed.code,
753
+ details: parsed,
754
+ requestId: (_response$headers$get = response.headers.get("x-request-id")) !== null && _response$headers$get !== void 0 ? _response$headers$get : void 0,
755
+ method: opts.method,
756
+ url
757
+ });
758
+ }
759
+ return response;
760
+ }
715
761
  buildUrl(path, query) {
716
762
  const cleanPath = path.replace(/^\/+/, "");
717
763
  let url = `${this.baseUrl}/${cleanPath}`;
@@ -722,19 +768,19 @@
722
768
  return url;
723
769
  }
724
770
  async buildHeaders(opts) {
725
- var _this3 = this;
726
- var _opts$accept;
727
- const headers = { "Accept": (_opts$accept = opts.accept) !== null && _opts$accept !== void 0 ? _opts$accept : "application/json" };
728
- if (_this3.auth.getAccessToken) {
729
- const token = await _this3.auth.getAccessToken();
771
+ var _this4 = this;
772
+ var _opts$accept2;
773
+ const headers = { "Accept": (_opts$accept2 = opts.accept) !== null && _opts$accept2 !== void 0 ? _opts$accept2 : "application/json" };
774
+ if (_this4.auth.getAccessToken) {
775
+ const token = await _this4.auth.getAccessToken();
730
776
  if (token) headers["Authorization"] = `Bearer ${token}`;
731
777
  }
732
- if (_this3.auth.getHeaders) {
733
- const authHeaders = await _this3.auth.getHeaders();
778
+ if (_this4.auth.getHeaders) {
779
+ const authHeaders = await _this4.auth.getHeaders();
734
780
  Object.assign(headers, authHeaders);
735
781
  }
736
- if (_this3.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this3.userAgent;
737
- const ct = _this3.resolveContentType(opts.contentType);
782
+ if (_this4.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this4.userAgent;
783
+ const ct = _this4.resolveContentType(opts.contentType);
738
784
  if (ct) headers["Content-Type"] = ct;
739
785
  return headers;
740
786
  }
@@ -743,24 +789,24 @@
743
789
  return contentType !== null && contentType !== void 0 ? contentType : "application/json";
744
790
  }
745
791
  async handleResponse(response, opts, url) {
746
- var _opts$expectedStatus2, _parsed;
747
- const expectedStatus = (_opts$expectedStatus2 = opts.expectedStatus) !== null && _opts$expectedStatus2 !== void 0 ? _opts$expectedStatus2 : 200;
792
+ var _opts$expectedStatus3, _parsed;
793
+ const expectedStatus = (_opts$expectedStatus3 = opts.expectedStatus) !== null && _opts$expectedStatus3 !== void 0 ? _opts$expectedStatus3 : 200;
748
794
  let bodyText = "";
749
795
  try {
750
796
  bodyText = await response.text();
751
- } catch (_unused) {}
797
+ } catch (_unused3) {}
752
798
  let parsed = null;
753
799
  if (bodyText) try {
754
800
  parsed = JSON.parse(bodyText);
755
- } catch (_unused2) {}
801
+ } catch (_unused4) {}
756
802
  if (response.status !== expectedStatus) {
757
- var _ref, _parsed$message, _response$headers$get;
803
+ var _ref2, _parsed$message2, _response$headers$get2;
758
804
  throw new CentiaApiError({
759
- message: ((_ref = (_parsed$message = parsed === null || parsed === void 0 ? void 0 : parsed.message) !== null && _parsed$message !== void 0 ? _parsed$message : parsed === null || parsed === void 0 ? void 0 : parsed.error) !== null && _ref !== void 0 ? _ref : bodyText) || `Unexpected status ${response.status}`,
805
+ message: ((_ref2 = (_parsed$message2 = parsed === null || parsed === void 0 ? void 0 : parsed.message) !== null && _parsed$message2 !== void 0 ? _parsed$message2 : parsed === null || parsed === void 0 ? void 0 : parsed.error) !== null && _ref2 !== void 0 ? _ref2 : bodyText) || `Unexpected status ${response.status}`,
760
806
  status: response.status,
761
807
  code: parsed === null || parsed === void 0 ? void 0 : parsed.code,
762
808
  details: parsed,
763
- requestId: (_response$headers$get = response.headers.get("x-request-id")) !== null && _response$headers$get !== void 0 ? _response$headers$get : void 0,
809
+ requestId: (_response$headers$get2 = response.headers.get("x-request-id")) !== null && _response$headers$get2 !== void 0 ? _response$headers$get2 : void 0,
764
810
  method: opts.method,
765
811
  url
766
812
  });
@@ -3391,6 +3437,265 @@
3391
3437
  }
3392
3438
  };
3393
3439
 
3440
+ //#endregion
3441
+ //#region src/snapshots/Snapshots.ts
3442
+ /**
3443
+ * Client for the snapshot API: GeoParquet exports of a relation.
3444
+ *
3445
+ * The job API (`/api/v4/snapshots`) queues and inspects exports and is
3446
+ * super-user only; exports run asynchronously — poll with `getSnapshot` or
3447
+ * use `waitForSnapshot`. The read API
3448
+ * (`/api/v4/schemas/{schema}/relations/{relation}/snapshots`) serves the
3449
+ * published snapshots to any user with read access to the relation, with
3450
+ * HEAD + byte-range support so Parquet readers can fetch footers and row
3451
+ * groups selectively.
3452
+ *
3453
+ * ```ts
3454
+ * const snapshots = new Snapshots(createCentiaClient({ baseUrl, auth }));
3455
+ * const { id } = await snapshots.postSnapshot({ schema: 'geodanmark', relation: 'bygning' });
3456
+ * const done = await snapshots.waitForSnapshot(id);
3457
+ * ```
3458
+ */
3459
+ var Snapshots = class {
3460
+ constructor(client) {
3461
+ this.client = client;
3462
+ }
3463
+ relationPath(schema, relation, rest = "") {
3464
+ return `api/v4/schemas/${encodeURIComponent(schema)}/relations/${encodeURIComponent(relation)}/snapshots${rest}`;
3465
+ }
3466
+ async postSnapshot(body) {
3467
+ return this.client.request({
3468
+ path: "api/v4/snapshots",
3469
+ method: "POST",
3470
+ body,
3471
+ expectedStatus: 202
3472
+ });
3473
+ }
3474
+ async getSnapshot(id) {
3475
+ var _this2 = this;
3476
+ const keys = Array.isArray(id) ? id : [id];
3477
+ return _this2.client.request({
3478
+ path: `api/v4/snapshots/${keys.map((k) => encodeURIComponent(k)).join(",")}`,
3479
+ method: "GET"
3480
+ });
3481
+ }
3482
+ /** List the newest snapshot jobs (50), optionally filtered by schema/relation. Super-user only. */
3483
+ async getSnapshots(options) {
3484
+ var _this3 = this;
3485
+ const query = {};
3486
+ if ((options === null || options === void 0 ? void 0 : options.schema) !== void 0) query.schema = options.schema;
3487
+ if ((options === null || options === void 0 ? void 0 : options.relation) !== void 0) query.relation = options.relation;
3488
+ return _this3.client.request({
3489
+ path: "api/v4/snapshots",
3490
+ method: "GET",
3491
+ query: Object.keys(query).length > 0 ? query : void 0
3492
+ });
3493
+ }
3494
+ /**
3495
+ * Poll a snapshot job until it reaches a terminal status (succeeded, failed
3496
+ * or superseded) and return it — check `status`/`error` on the result.
3497
+ * Throws an `Error` when `timeoutMs` elapses first.
3498
+ */
3499
+ async waitForSnapshot(id, options) {
3500
+ var _this4 = this;
3501
+ var _options$intervalMs, _options$timeoutMs;
3502
+ const intervalMs = (_options$intervalMs = options === null || options === void 0 ? void 0 : options.intervalMs) !== null && _options$intervalMs !== void 0 ? _options$intervalMs : 2e3;
3503
+ const timeoutMs = (_options$timeoutMs = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _options$timeoutMs !== void 0 ? _options$timeoutMs : 3e5;
3504
+ const deadline = Date.now() + timeoutMs;
3505
+ for (;;) {
3506
+ const job = await _this4.getSnapshot(id);
3507
+ if (job.status === "succeeded" || job.status === "failed" || job.status === "superseded") return job;
3508
+ if (Date.now() + intervalMs > deadline) throw new Error(`Timed out after ${timeoutMs} ms waiting for snapshot ${id} (status: ${job.status})`);
3509
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3510
+ }
3511
+ }
3512
+ /** List the published snapshots of a relation, newest first. */
3513
+ async getRelationSnapshots(schema, relation) {
3514
+ var _this5 = this;
3515
+ return _this5.client.request({
3516
+ path: _this5.relationPath(schema, relation),
3517
+ method: "GET"
3518
+ });
3519
+ }
3520
+ /** Get one published snapshot (metadata) by date (`YYYY-MM-DD`). */
3521
+ async getRelationSnapshot(schema, relation, date) {
3522
+ var _this6 = this;
3523
+ return _this6.client.request({
3524
+ path: _this6.relationPath(schema, relation, `/${encodeURIComponent(date)}`),
3525
+ method: "GET"
3526
+ });
3527
+ }
3528
+ /**
3529
+ * Absolute URL of the snapshot's Parquet file, without fetching it — for
3530
+ * DuckDB/GDAL or a plain fetch. Authorization travels in the request
3531
+ * header, so protected relations need the caller to attach the token.
3532
+ */
3533
+ getRelationSnapshotDataUrl(schema, relation, date) {
3534
+ return `${this.client.baseUrl}/${this.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`)}`;
3535
+ }
3536
+ /**
3537
+ * Fetch the snapshot's Parquet file and return the raw `Response` (stream
3538
+ * or buffer it yourself). Follows the 302 redirect the server answers in
3539
+ * redirect mode. Pass `range` for a single byte range (206). Throws
3540
+ * `CentiaApiError`: 403, 404, 409 (several files; use
3541
+ * getRelationSnapshotFile), 416, 502.
3542
+ */
3543
+ async getRelationSnapshotData(schema, relation, date, options) {
3544
+ var _this7 = this;
3545
+ return _this7.rawGet(_this7.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`), "GET", options);
3546
+ }
3547
+ /** HEAD request for the snapshot's Parquet file — Content-Length, Accept-Ranges and ETag without the body. */
3548
+ async headRelationSnapshotData(schema, relation, date) {
3549
+ var _this8 = this;
3550
+ return _this8.rawGet(_this8.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`), "HEAD");
3551
+ }
3552
+ /**
3553
+ * Fetch one file of the snapshot by catalog name (`data-<id>.parquet`,
3554
+ * `metadata-<id>.json`). Same redirect/range semantics as
3555
+ * getRelationSnapshotData.
3556
+ */
3557
+ async getRelationSnapshotFile(schema, relation, date, file, options) {
3558
+ var _this9 = this;
3559
+ return _this9.rawGet(_this9.relationPath(schema, relation, `/${encodeURIComponent(date)}/files/${encodeURIComponent(file)}`), "GET", options);
3560
+ }
3561
+ /** HEAD request for one file of the snapshot. */
3562
+ async headRelationSnapshotFile(schema, relation, date, file) {
3563
+ var _this10 = this;
3564
+ return _this10.rawGet(_this10.relationPath(schema, relation, `/${encodeURIComponent(date)}/files/${encodeURIComponent(file)}`), "HEAD");
3565
+ }
3566
+ async rawGet(path, method, options) {
3567
+ var _this11 = this;
3568
+ const headers = {};
3569
+ if (options === null || options === void 0 ? void 0 : options.range) headers["Range"] = `bytes=${options.range[0]}-${options.range[1]}`;
3570
+ return _this11.client.requestRaw({
3571
+ path,
3572
+ method,
3573
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
3574
+ expectedStatus: [200, 206]
3575
+ });
3576
+ }
3577
+ };
3578
+
3579
+ //#endregion
3580
+ //#region src/scheduler/Scheduler.ts
3581
+ function idsPath(id) {
3582
+ return (Array.isArray(id) ? id : [id]).map((k) => encodeURIComponent(String(k))).join(",");
3583
+ }
3584
+ /**
3585
+ * Client for the scheduler API (`/api/v4/scheduler`). Super-user only.
3586
+ *
3587
+ * Jobs describe recurring data imports (cron-scheduled); runs are their
3588
+ * executions. Starting a run is asynchronous — poll `getSchedulerRuns`
3589
+ * (the `_links.runs` of the 202) until the run finishes.
3590
+ *
3591
+ * ```ts
3592
+ * const scheduler = new Scheduler(createCentiaClient({ baseUrl, auth }));
3593
+ * const { ids } = await scheduler.postSchedulerJob({ name, schema, url, schedule: '0 3 * * *' });
3594
+ * await scheduler.postSchedulerRun({ job: ids[0] });
3595
+ * ```
3596
+ */
3597
+ var Scheduler = class {
3598
+ constructor(client) {
3599
+ this.client = client;
3600
+ }
3601
+ /** List all scheduler jobs. */
3602
+ async getSchedulerJobs() {
3603
+ return this.client.request({
3604
+ path: "api/v4/scheduler/jobs",
3605
+ method: "GET"
3606
+ });
3607
+ }
3608
+ async getSchedulerJob(id) {
3609
+ return this.client.request({
3610
+ path: `api/v4/scheduler/jobs/${idsPath(id)}`,
3611
+ method: "GET"
3612
+ });
3613
+ }
3614
+ /**
3615
+ * Create one or more jobs (201; all-or-nothing for arrays). Returns the
3616
+ * Location header and the new ids parsed from it, in request order.
3617
+ */
3618
+ async postSchedulerJob(body) {
3619
+ var _this3 = this;
3620
+ var _res$getHeader, _location$split$pop;
3621
+ const location = (_res$getHeader = (await _this3.client.requestFull({
3622
+ path: "api/v4/scheduler/jobs",
3623
+ method: "POST",
3624
+ body,
3625
+ expectedStatus: 201
3626
+ })).getHeader("Location")) !== null && _res$getHeader !== void 0 ? _res$getHeader : "";
3627
+ return {
3628
+ location,
3629
+ ids: ((_location$split$pop = location.split("/").pop()) !== null && _location$split$pop !== void 0 ? _location$split$pop : "").split(",").map((s) => Number(s)).filter((n) => Number.isFinite(n))
3630
+ };
3631
+ }
3632
+ /** Partially update a job (303 + Location). */
3633
+ async patchSchedulerJob(id, body) {
3634
+ var _this4 = this;
3635
+ var _res$getHeader2;
3636
+ return { location: (_res$getHeader2 = (await _this4.client.requestFull({
3637
+ path: `api/v4/scheduler/jobs/${idsPath(id)}`,
3638
+ method: "PATCH",
3639
+ body,
3640
+ expectedStatus: 303
3641
+ })).getHeader("Location")) !== null && _res$getHeader2 !== void 0 ? _res$getHeader2 : "" };
3642
+ }
3643
+ /**
3644
+ * Delete one or more jobs (204). Nothing is deleted if any id fails:
3645
+ * throws 404 (unknown id) or 409 (a run is in progress).
3646
+ */
3647
+ async deleteSchedulerJob(id) {
3648
+ await this.client.request({
3649
+ path: `api/v4/scheduler/jobs/${idsPath(id)}`,
3650
+ method: "DELETE",
3651
+ expectedStatus: 204
3652
+ });
3653
+ }
3654
+ /** List runs (running plus the newest 50 finished), optionally filtered by job and status. */
3655
+ async getSchedulerRuns(options) {
3656
+ var _this6 = this;
3657
+ const query = {};
3658
+ if ((options === null || options === void 0 ? void 0 : options.job) !== void 0) query.job = String(options.job);
3659
+ if ((options === null || options === void 0 ? void 0 : options.status) !== void 0) query.status = options.status;
3660
+ return _this6.client.request({
3661
+ path: "api/v4/scheduler/runs",
3662
+ method: "GET",
3663
+ query: Object.keys(query).length > 0 ? query : void 0
3664
+ });
3665
+ }
3666
+ /** Get one run by uuid. */
3667
+ async getSchedulerRun(uuid) {
3668
+ return this.client.request({
3669
+ path: `api/v4/scheduler/runs/${encodeURIComponent(uuid)}`,
3670
+ method: "GET"
3671
+ });
3672
+ }
3673
+ /**
3674
+ * Start a run of a job (202). Throws 404 (unknown job) or 409 (a run of
3675
+ * the job is already running).
3676
+ */
3677
+ async postSchedulerRun(body) {
3678
+ return this.client.request({
3679
+ path: "api/v4/scheduler/runs",
3680
+ method: "POST",
3681
+ body,
3682
+ expectedStatus: 202
3683
+ });
3684
+ }
3685
+ /**
3686
+ * Stop a running run: sends SIGINT, which the server escalates to SIGKILL
3687
+ * after 30 s — the request itself can take up to ~30 s, so do not use a
3688
+ * short timeout. Throws 404 (no running run with that uuid) or 409 (the
3689
+ * run is on another host).
3690
+ */
3691
+ async deleteSchedulerRun(uuid) {
3692
+ return this.client.request({
3693
+ path: `api/v4/scheduler/runs/${encodeURIComponent(uuid)}`,
3694
+ method: "DELETE"
3695
+ });
3696
+ }
3697
+ };
3698
+
3394
3699
  //#endregion
3395
3700
  //#region src/auth/errors.ts
3396
3701
  /**
@@ -3528,8 +3833,10 @@ exports.Ogc = Ogc;
3528
3833
  exports.Ows = Ows;
3529
3834
  exports.PasswordFlow = PasswordFlow;
3530
3835
  exports.Rpc = Rpc;
3836
+ exports.Scheduler = Scheduler;
3531
3837
  exports.SessionExpiredError = SessionExpiredError;
3532
3838
  exports.SignUp = SignUp;
3839
+ exports.Snapshots = Snapshots;
3533
3840
  exports.Sql = Sql;
3534
3841
  exports.SqlNoToken = SqlNoToken;
3535
3842
  exports.Stats = Stats;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centia-io/sdk",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Centia-io TypeScript SDK",
5
5
  "author": "Martin Høgh",
6
6
  "license": "MIT",