@centia-io/sdk 0.2.13 → 0.2.15

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.
@@ -340,6 +340,9 @@ var Gc2Service = class {
340
340
  isSignUpOptions(options) {
341
341
  return "parentDb" in options;
342
342
  }
343
+ isGuestFlowOptions(options) {
344
+ return "database" in options && !("username" in options);
345
+ }
343
346
  buildUrl(path) {
344
347
  if (path.startsWith("http://") || path.startsWith("https://")) return path;
345
348
  return `${this.host}${path}`;
@@ -452,12 +455,24 @@ var Gc2Service = class {
452
455
  database
453
456
  });
454
457
  }
455
- async getRefreshToken(token) {
458
+ async getGuestToken() {
456
459
  var _this5 = this;
457
- var _this$options$tokenUr3;
458
- const path = (_this$options$tokenUr3 = _this5.options.tokenUri) !== null && _this$options$tokenUr3 !== void 0 ? _this$options$tokenUr3 : `${_this5.host}/api/v4/oauth`;
460
+ let database;
461
+ if (_this5.isGuestFlowOptions(_this5.options)) database = _this5.options.database;
462
+ else throw new Error("GuestFlow options required for this operation");
463
+ const path = `${_this5.host}/api/v4/oauth/guest`;
459
464
  return _this5.request(_this5.buildUrl(path), "POST", {
465
+ database,
460
466
  client_id: _this5.options.clientId,
467
+ client_secret: _this5.options.clientSecret
468
+ });
469
+ }
470
+ async getRefreshToken(token) {
471
+ var _this6 = this;
472
+ var _this$options$tokenUr3;
473
+ const path = (_this$options$tokenUr3 = _this6.options.tokenUri) !== null && _this$options$tokenUr3 !== void 0 ? _this$options$tokenUr3 : `${_this6.host}/api/v4/oauth`;
474
+ return _this6.request(_this6.buildUrl(path), "POST", {
475
+ client_id: _this6.options.clientId,
461
476
  grant_type: "refresh_token",
462
477
  refresh_token: token
463
478
  });
@@ -575,6 +590,48 @@ var PasswordFlow = class {
575
590
  }
576
591
  };
577
592
 
593
+ //#endregion
594
+ //#region src/GuestFlow.ts
595
+ /**
596
+ * @author Martin Høgh <mh@mapcentia.com>
597
+ * @copyright 2013-2026 MapCentia ApS
598
+ * @license https://opensource.org/license/mit The MIT License
599
+ *
600
+ */
601
+ /**
602
+ * Guest token flow. Issues access/refresh tokens for the database's default
603
+ * user (the sub-user used for anonymous access) without any user credentials.
604
+ * `clientSecret` is only required when the OAuth client is not public.
605
+ */
606
+ var GuestFlow = class {
607
+ constructor(options) {
608
+ this.options = options;
609
+ this.service = new Gc2Service(options);
610
+ }
611
+ async signIn() {
612
+ var _this = this;
613
+ const { access_token, refresh_token } = await _this.service.getGuestToken();
614
+ setTokens({
615
+ accessToken: access_token,
616
+ refreshToken: refresh_token
617
+ });
618
+ setOptions({
619
+ clientId: _this.options.clientId,
620
+ host: _this.options.host,
621
+ redirectUri: "",
622
+ clientSecret: _this.options.clientSecret
623
+ });
624
+ }
625
+ signOut() {
626
+ this.clear();
627
+ }
628
+ clear() {
629
+ clearTokens();
630
+ clearOptions();
631
+ clearNonce();
632
+ }
633
+ };
634
+
578
635
  //#endregion
579
636
  //#region src/http/errors.ts
580
637
  /**
@@ -649,6 +706,52 @@ var CentiaHttpClient = class {
649
706
  getHeader: (name) => response.headers.get(name)
650
707
  };
651
708
  }
709
+ /**
710
+ * Execute a request and return the raw Response without consuming the body.
711
+ * For binary endpoints (file downloads): follows redirects (a presigned
712
+ * storage URL — the fetch spec strips Authorization on cross-origin
713
+ * redirects), sends no Content-Type, and supports extra request headers
714
+ * such as Range. Throws CentiaApiError on a status not in expectedStatus.
715
+ */
716
+ async requestRaw(opts) {
717
+ var _this3 = this;
718
+ var _opts$accept, _opts$expectedStatus2;
719
+ const url = _this3.buildUrl(opts.path, opts.query);
720
+ const headers = { "Accept": (_opts$accept = opts.accept) !== null && _opts$accept !== void 0 ? _opts$accept : "*/*" };
721
+ if (_this3.auth.getAccessToken) {
722
+ const token = await _this3.auth.getAccessToken();
723
+ if (token) headers["Authorization"] = `Bearer ${token}`;
724
+ }
725
+ if (_this3.auth.getHeaders) Object.assign(headers, await _this3.auth.getHeaders());
726
+ if (_this3.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this3.userAgent;
727
+ Object.assign(headers, opts.headers);
728
+ const response = await _this3.fetchFn(url, {
729
+ method: opts.method,
730
+ headers,
731
+ redirect: "follow"
732
+ });
733
+ if (!((_opts$expectedStatus2 = opts.expectedStatus) !== null && _opts$expectedStatus2 !== void 0 ? _opts$expectedStatus2 : [200]).includes(response.status)) {
734
+ var _ref, _parsed$message, _response$headers$get;
735
+ let bodyText = "";
736
+ try {
737
+ bodyText = await response.text();
738
+ } catch (_unused) {}
739
+ let parsed = null;
740
+ if (bodyText) try {
741
+ parsed = JSON.parse(bodyText);
742
+ } catch (_unused2) {}
743
+ throw new CentiaApiError({
744
+ 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}`,
745
+ status: response.status,
746
+ code: parsed === null || parsed === void 0 ? void 0 : parsed.code,
747
+ details: parsed,
748
+ requestId: (_response$headers$get = response.headers.get("x-request-id")) !== null && _response$headers$get !== void 0 ? _response$headers$get : void 0,
749
+ method: opts.method,
750
+ url
751
+ });
752
+ }
753
+ return response;
754
+ }
652
755
  buildUrl(path, query) {
653
756
  const cleanPath = path.replace(/^\/+/, "");
654
757
  let url = `${this.baseUrl}/${cleanPath}`;
@@ -659,19 +762,19 @@ var CentiaHttpClient = class {
659
762
  return url;
660
763
  }
661
764
  async buildHeaders(opts) {
662
- var _this3 = this;
663
- var _opts$accept;
664
- const headers = { "Accept": (_opts$accept = opts.accept) !== null && _opts$accept !== void 0 ? _opts$accept : "application/json" };
665
- if (_this3.auth.getAccessToken) {
666
- const token = await _this3.auth.getAccessToken();
765
+ var _this4 = this;
766
+ var _opts$accept2;
767
+ const headers = { "Accept": (_opts$accept2 = opts.accept) !== null && _opts$accept2 !== void 0 ? _opts$accept2 : "application/json" };
768
+ if (_this4.auth.getAccessToken) {
769
+ const token = await _this4.auth.getAccessToken();
667
770
  if (token) headers["Authorization"] = `Bearer ${token}`;
668
771
  }
669
- if (_this3.auth.getHeaders) {
670
- const authHeaders = await _this3.auth.getHeaders();
772
+ if (_this4.auth.getHeaders) {
773
+ const authHeaders = await _this4.auth.getHeaders();
671
774
  Object.assign(headers, authHeaders);
672
775
  }
673
- if (_this3.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this3.userAgent;
674
- const ct = _this3.resolveContentType(opts.contentType);
776
+ if (_this4.userAgent && typeof navigator === "undefined") headers["User-Agent"] = _this4.userAgent;
777
+ const ct = _this4.resolveContentType(opts.contentType);
675
778
  if (ct) headers["Content-Type"] = ct;
676
779
  return headers;
677
780
  }
@@ -680,24 +783,24 @@ var CentiaHttpClient = class {
680
783
  return contentType !== null && contentType !== void 0 ? contentType : "application/json";
681
784
  }
682
785
  async handleResponse(response, opts, url) {
683
- var _opts$expectedStatus2, _parsed;
684
- const expectedStatus = (_opts$expectedStatus2 = opts.expectedStatus) !== null && _opts$expectedStatus2 !== void 0 ? _opts$expectedStatus2 : 200;
786
+ var _opts$expectedStatus3, _parsed;
787
+ const expectedStatus = (_opts$expectedStatus3 = opts.expectedStatus) !== null && _opts$expectedStatus3 !== void 0 ? _opts$expectedStatus3 : 200;
685
788
  let bodyText = "";
686
789
  try {
687
790
  bodyText = await response.text();
688
- } catch (_unused) {}
791
+ } catch (_unused3) {}
689
792
  let parsed = null;
690
793
  if (bodyText) try {
691
794
  parsed = JSON.parse(bodyText);
692
- } catch (_unused2) {}
795
+ } catch (_unused4) {}
693
796
  if (response.status !== expectedStatus) {
694
- var _ref, _parsed$message, _response$headers$get;
797
+ var _ref2, _parsed$message2, _response$headers$get2;
695
798
  throw new CentiaApiError({
696
- 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}`,
799
+ 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}`,
697
800
  status: response.status,
698
801
  code: parsed === null || parsed === void 0 ? void 0 : parsed.code,
699
802
  details: parsed,
700
- requestId: (_response$headers$get = response.headers.get("x-request-id")) !== null && _response$headers$get !== void 0 ? _response$headers$get : void 0,
803
+ requestId: (_response$headers$get2 = response.headers.get("x-request-id")) !== null && _response$headers$get2 !== void 0 ? _response$headers$get2 : void 0,
701
804
  method: opts.method,
702
805
  url
703
806
  });
@@ -3328,6 +3431,150 @@ var Features = class {
3328
3431
  }
3329
3432
  };
3330
3433
 
3434
+ //#endregion
3435
+ //#region src/snapshots/Snapshots.ts
3436
+ /**
3437
+ * Client for the snapshot API: GeoParquet exports of a relation.
3438
+ *
3439
+ * The job API (`/api/v4/snapshots`) queues and inspects exports and is
3440
+ * super-user only; exports run asynchronously — poll with `getSnapshot` or
3441
+ * use `waitForSnapshot`. The read API
3442
+ * (`/api/v4/schemas/{schema}/relations/{relation}/snapshots`) serves the
3443
+ * published snapshots to any user with read access to the relation, with
3444
+ * HEAD + byte-range support so Parquet readers can fetch footers and row
3445
+ * groups selectively.
3446
+ *
3447
+ * ```ts
3448
+ * const snapshots = new Snapshots(createCentiaClient({ baseUrl, auth }));
3449
+ * const { id } = await snapshots.postSnapshot({ schema: 'geodanmark', relation: 'bygning' });
3450
+ * const done = await snapshots.waitForSnapshot(id);
3451
+ * ```
3452
+ */
3453
+ var Snapshots = class {
3454
+ constructor(client) {
3455
+ this.client = client;
3456
+ }
3457
+ relationPath(schema, relation, rest = "") {
3458
+ return `api/v4/schemas/${encodeURIComponent(schema)}/relations/${encodeURIComponent(relation)}/snapshots${rest}`;
3459
+ }
3460
+ /**
3461
+ * Queue a Parquet snapshot of a table or view. Returns 202 with the job id.
3462
+ * Throws `CentiaApiError`: 403 (super-user only), 404 (relation not found),
3463
+ * 409 (a snapshot of the relation is already pending or running),
3464
+ * 501 (snapshot storage not configured).
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
+ /** Get a snapshot job by id. Super-user only. */
3475
+ async getSnapshot(id) {
3476
+ return this.client.request({
3477
+ path: `api/v4/snapshots/${encodeURIComponent(id)}`,
3478
+ method: "GET"
3479
+ });
3480
+ }
3481
+ /** List the newest snapshot jobs (50), optionally filtered by schema/relation. Super-user only. */
3482
+ async getSnapshots(options) {
3483
+ var _this3 = this;
3484
+ const query = {};
3485
+ if ((options === null || options === void 0 ? void 0 : options.schema) !== void 0) query.schema = options.schema;
3486
+ if ((options === null || options === void 0 ? void 0 : options.relation) !== void 0) query.relation = options.relation;
3487
+ return _this3.client.request({
3488
+ path: "api/v4/snapshots",
3489
+ method: "GET",
3490
+ query: Object.keys(query).length > 0 ? query : void 0
3491
+ });
3492
+ }
3493
+ /**
3494
+ * Poll a snapshot job until it reaches a terminal status (succeeded, failed
3495
+ * or superseded) and return it — check `status`/`error` on the result.
3496
+ * Throws an `Error` when `timeoutMs` elapses first.
3497
+ */
3498
+ async waitForSnapshot(id, options) {
3499
+ var _this4 = this;
3500
+ var _options$intervalMs, _options$timeoutMs;
3501
+ const intervalMs = (_options$intervalMs = options === null || options === void 0 ? void 0 : options.intervalMs) !== null && _options$intervalMs !== void 0 ? _options$intervalMs : 2e3;
3502
+ const timeoutMs = (_options$timeoutMs = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _options$timeoutMs !== void 0 ? _options$timeoutMs : 3e5;
3503
+ const deadline = Date.now() + timeoutMs;
3504
+ for (;;) {
3505
+ const job = await _this4.getSnapshot(id);
3506
+ if (job.status === "succeeded" || job.status === "failed" || job.status === "superseded") return job;
3507
+ if (Date.now() + intervalMs > deadline) throw new Error(`Timed out after ${timeoutMs} ms waiting for snapshot ${id} (status: ${job.status})`);
3508
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3509
+ }
3510
+ }
3511
+ /** List the published snapshots of a relation, newest first. */
3512
+ async getRelationSnapshots(schema, relation) {
3513
+ var _this5 = this;
3514
+ return _this5.client.request({
3515
+ path: _this5.relationPath(schema, relation),
3516
+ method: "GET"
3517
+ });
3518
+ }
3519
+ /** Get one published snapshot (metadata) by date (`YYYY-MM-DD`). */
3520
+ async getRelationSnapshot(schema, relation, date) {
3521
+ var _this6 = this;
3522
+ return _this6.client.request({
3523
+ path: _this6.relationPath(schema, relation, `/${encodeURIComponent(date)}`),
3524
+ method: "GET"
3525
+ });
3526
+ }
3527
+ /**
3528
+ * Absolute URL of the snapshot's Parquet file, without fetching it — for
3529
+ * DuckDB/GDAL or a plain fetch. Authorization travels in the request
3530
+ * header, so protected relations need the caller to attach the token.
3531
+ */
3532
+ getRelationSnapshotDataUrl(schema, relation, date) {
3533
+ return `${this.client.baseUrl}/${this.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`)}`;
3534
+ }
3535
+ /**
3536
+ * Fetch the snapshot's Parquet file and return the raw `Response` (stream
3537
+ * or buffer it yourself). Follows the 302 redirect the server answers in
3538
+ * redirect mode. Pass `range` for a single byte range (206). Throws
3539
+ * `CentiaApiError`: 403, 404, 409 (several files; use
3540
+ * getRelationSnapshotFile), 416, 502.
3541
+ */
3542
+ async getRelationSnapshotData(schema, relation, date, options) {
3543
+ var _this7 = this;
3544
+ return _this7.rawGet(_this7.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`), "GET", options);
3545
+ }
3546
+ /** HEAD request for the snapshot's Parquet file — Content-Length, Accept-Ranges and ETag without the body. */
3547
+ async headRelationSnapshotData(schema, relation, date) {
3548
+ var _this8 = this;
3549
+ return _this8.rawGet(_this8.relationPath(schema, relation, `/${encodeURIComponent(date)}/data`), "HEAD");
3550
+ }
3551
+ /**
3552
+ * Fetch one file of the snapshot by catalog name (`data-<id>.parquet`,
3553
+ * `metadata-<id>.json`). Same redirect/range semantics as
3554
+ * getRelationSnapshotData.
3555
+ */
3556
+ async getRelationSnapshotFile(schema, relation, date, file, options) {
3557
+ var _this9 = this;
3558
+ return _this9.rawGet(_this9.relationPath(schema, relation, `/${encodeURIComponent(date)}/files/${encodeURIComponent(file)}`), "GET", options);
3559
+ }
3560
+ /** HEAD request for one file of the snapshot. */
3561
+ async headRelationSnapshotFile(schema, relation, date, file) {
3562
+ var _this10 = this;
3563
+ return _this10.rawGet(_this10.relationPath(schema, relation, `/${encodeURIComponent(date)}/files/${encodeURIComponent(file)}`), "HEAD");
3564
+ }
3565
+ async rawGet(path, method, options) {
3566
+ var _this11 = this;
3567
+ const headers = {};
3568
+ if (options === null || options === void 0 ? void 0 : options.range) headers["Range"] = `bytes=${options.range[0]}-${options.range[1]}`;
3569
+ return _this11.client.requestRaw({
3570
+ path,
3571
+ method,
3572
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
3573
+ expectedStatus: [200, 206]
3574
+ });
3575
+ }
3576
+ };
3577
+
3331
3578
  //#endregion
3332
3579
  //#region src/auth/errors.ts
3333
3580
  /**
@@ -3450,5 +3697,5 @@ function isExpired(jwt, skewSeconds) {
3450
3697
  */
3451
3698
 
3452
3699
  //#endregion
3453
- export { CentiaApiError, Claims, CodeFlow, Features, Gql, Keyvalue, Mapcache, Meta, NotLoggedInError, OGC_CRS84, Ogc, Ows, PasswordFlow, Rpc, SessionExpiredError, SignUp, Sql, SqlNoToken, Stats, Status, Tables, Users, Wfs, Ws, createApi, createCentiaAdminClient, createCentiaClient, createSqlBuilder, createTokenProvider, isCentiaApiError, ogcEpsgCrs };
3700
+ export { CentiaApiError, Claims, CodeFlow, Features, Gql, GuestFlow, Keyvalue, Mapcache, Meta, NotLoggedInError, OGC_CRS84, Ogc, Ows, PasswordFlow, Rpc, SessionExpiredError, SignUp, Snapshots, Sql, SqlNoToken, Stats, Status, Tables, Users, Wfs, Ws, createApi, createCentiaAdminClient, createCentiaClient, createSqlBuilder, createTokenProvider, isCentiaApiError, ogcEpsgCrs };
3454
3701
  //# sourceMappingURL=centia-io-sdk.js.map