@centia-io/sdk 0.2.12 → 0.2.14
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/README.md +57 -0
- package/dist/centia-io-sdk.cjs +216 -4
- package/dist/centia-io-sdk.d.cts +241 -52
- package/dist/centia-io-sdk.d.cts.map +1 -1
- package/dist/centia-io-sdk.d.ts +241 -52
- package/dist/centia-io-sdk.d.ts.map +1 -1
- package/dist/centia-io-sdk.js +212 -4
- package/dist/centia-io-sdk.js.map +1 -1
- package/dist/centia-io-sdk.umd.js +215 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,6 +115,33 @@ await flow.signIn();
|
|
|
115
115
|
flow.signOut(); // Clears tokens/options in local storage (no redirect)
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
### GuestFlow (Guest tokens – anonymous access)
|
|
119
|
+
|
|
120
|
+
Obtain tokens for a database's default user (the sub-user used for anonymous access) without any user credentials. Useful for public apps that need a bearer token for public data. Requires that the database has a default user; `clientSecret` is only needed when the OAuth client is not public.
|
|
121
|
+
|
|
122
|
+
Required options:
|
|
123
|
+
- `host`
|
|
124
|
+
- `clientId`
|
|
125
|
+
- `database`
|
|
126
|
+
- `clientSecret` (only for confidential clients)
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
```ts
|
|
130
|
+
import { GuestFlow } from "@centia-io/sdk";
|
|
131
|
+
|
|
132
|
+
const flow = new GuestFlow({
|
|
133
|
+
host: "https://api.centia.io",
|
|
134
|
+
clientId: "your-client-id",
|
|
135
|
+
database: "your-database"
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await flow.signIn();
|
|
139
|
+
// Tokens for the default user are now stored; subsequent Sql/Rpc calls
|
|
140
|
+
// include the Authorization header and refresh works as usual.
|
|
141
|
+
|
|
142
|
+
flow.signOut(); // Clears tokens/options in local storage (no redirect)
|
|
143
|
+
```
|
|
144
|
+
|
|
118
145
|
### SignUp (Browser – Create a new user)
|
|
119
146
|
|
|
120
147
|
Use this helper in browser applications to redirect the user to the Centia‑io sign‑up page.
|
|
@@ -474,6 +501,36 @@ const job = await mapcache.deleteMapcacheTileset('my_database', 'my_schema.roads
|
|
|
474
501
|
|
|
475
502
|
The endpoint accepts anonymous, HTTP Basic and Bearer token requests; tile requests are authorized against the tileset's layer. Authorization travels in the `Authorization` header — it cannot be embedded in the URL — so for protected tilesets, inject the header per tile request via the map library's request hook (e.g. MapLibre's `transformRequest` or OpenLayers' `tileLoadFunction`).
|
|
476
503
|
|
|
504
|
+
## OGC API (Features / Maps)
|
|
505
|
+
|
|
506
|
+
`Ogc` wraps the RESTful OGC API under `/api/v4/ogc/database/{database}` — OGC API Features (Part 1 Core, Part 2 CRS) for reading features as GeoJSON and OGC API Maps (Part 1 Core) for rendered images. Like `Ows`/`Wfs` it takes a `CentiaHttpClient`; Bearer token, HTTP Basic and anonymous requests are all accepted:
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
import { createCentiaClient, Ogc, OGC_CRS84, ogcEpsgCrs } from '@centia-io/sdk'
|
|
510
|
+
|
|
511
|
+
const ogc = new Ogc(http)
|
|
512
|
+
|
|
513
|
+
const { collections } = await ogc.getCollections('my_database')
|
|
514
|
+
const collection = await ogc.getCollection('my_database', 'my_schema.roads') // extent, crs list, links
|
|
515
|
+
|
|
516
|
+
// GeoJSON items — default page size is 10; follow the `next` link or pass offset
|
|
517
|
+
const page = await ogc.getItems<{ gid: number; name: string }>('my_database', 'my_schema.roads', {
|
|
518
|
+
bbox: [9, 55, 10, 56], // lon/lat in CRS84 (the default bbox-crs)
|
|
519
|
+
crs: ogcEpsgCrs(25832), // output CRS from the collection's crs list
|
|
520
|
+
limit: 100,
|
|
521
|
+
datetime: '2024-01-01T00:00:00Z', // versioned layers: the version valid at that time
|
|
522
|
+
})
|
|
523
|
+
page.numberMatched // total; page.numberReturned; page.links (next/prev)
|
|
524
|
+
|
|
525
|
+
const feature = await ogc.getItem('my_database', 'my_schema.roads', 42)
|
|
526
|
+
|
|
527
|
+
// Map images are fetched by URL (e.g. an <img> or a map library)
|
|
528
|
+
const url = ogc.mapUrl('my_database', 'my_schema.roads', { bbox: [9, 55, 10, 56], width: 512, format: 'png' })
|
|
529
|
+
const multi = ogc.datasetMapUrl('my_database', ['my_schema.roads', 'my_schema.buildings'], { width: 512 })
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
CRS values are URIs: `OGC_CRS84` (lon/lat, default) or `ogcEpsgCrs(code)`; note `ogcEpsgCrs(4326)` is lat/lon order per OGC API Features Part 2. A collection that exists but is not readable answers `401` (anonymous) or `403` (no privilege); unknown collections and features are `404` — all thrown as `CentiaApiError`. Geofence rules, versioning and workflow are applied server-side.
|
|
533
|
+
|
|
477
534
|
## Key/value store
|
|
478
535
|
|
|
479
536
|
`Keyvalue` wraps the `/api/v4/keyvalue` endpoints for storing arbitrary JSON under globally unique keys. It takes a `CentiaHttpClient` and requires a Bearer token:
|
package/dist/centia-io-sdk.cjs
CHANGED
|
@@ -341,6 +341,9 @@ var Gc2Service = class {
|
|
|
341
341
|
isSignUpOptions(options) {
|
|
342
342
|
return "parentDb" in options;
|
|
343
343
|
}
|
|
344
|
+
isGuestFlowOptions(options) {
|
|
345
|
+
return "database" in options && !("username" in options);
|
|
346
|
+
}
|
|
344
347
|
buildUrl(path) {
|
|
345
348
|
if (path.startsWith("http://") || path.startsWith("https://")) return path;
|
|
346
349
|
return `${this.host}${path}`;
|
|
@@ -453,12 +456,24 @@ var Gc2Service = class {
|
|
|
453
456
|
database
|
|
454
457
|
});
|
|
455
458
|
}
|
|
456
|
-
async
|
|
459
|
+
async getGuestToken() {
|
|
457
460
|
var _this5 = this;
|
|
458
|
-
|
|
459
|
-
|
|
461
|
+
let database;
|
|
462
|
+
if (_this5.isGuestFlowOptions(_this5.options)) database = _this5.options.database;
|
|
463
|
+
else throw new Error("GuestFlow options required for this operation");
|
|
464
|
+
const path = `${_this5.host}/api/v4/oauth/guest`;
|
|
460
465
|
return _this5.request(_this5.buildUrl(path), "POST", {
|
|
466
|
+
database,
|
|
461
467
|
client_id: _this5.options.clientId,
|
|
468
|
+
client_secret: _this5.options.clientSecret
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async getRefreshToken(token) {
|
|
472
|
+
var _this6 = this;
|
|
473
|
+
var _this$options$tokenUr3;
|
|
474
|
+
const path = (_this$options$tokenUr3 = _this6.options.tokenUri) !== null && _this$options$tokenUr3 !== void 0 ? _this$options$tokenUr3 : `${_this6.host}/api/v4/oauth`;
|
|
475
|
+
return _this6.request(_this6.buildUrl(path), "POST", {
|
|
476
|
+
client_id: _this6.options.clientId,
|
|
462
477
|
grant_type: "refresh_token",
|
|
463
478
|
refresh_token: token
|
|
464
479
|
});
|
|
@@ -576,6 +591,48 @@ var PasswordFlow = class {
|
|
|
576
591
|
}
|
|
577
592
|
};
|
|
578
593
|
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/GuestFlow.ts
|
|
596
|
+
/**
|
|
597
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
598
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
599
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
600
|
+
*
|
|
601
|
+
*/
|
|
602
|
+
/**
|
|
603
|
+
* Guest token flow. Issues access/refresh tokens for the database's default
|
|
604
|
+
* user (the sub-user used for anonymous access) without any user credentials.
|
|
605
|
+
* `clientSecret` is only required when the OAuth client is not public.
|
|
606
|
+
*/
|
|
607
|
+
var GuestFlow = class {
|
|
608
|
+
constructor(options) {
|
|
609
|
+
this.options = options;
|
|
610
|
+
this.service = new Gc2Service(options);
|
|
611
|
+
}
|
|
612
|
+
async signIn() {
|
|
613
|
+
var _this = this;
|
|
614
|
+
const { access_token, refresh_token } = await _this.service.getGuestToken();
|
|
615
|
+
setTokens({
|
|
616
|
+
accessToken: access_token,
|
|
617
|
+
refreshToken: refresh_token
|
|
618
|
+
});
|
|
619
|
+
setOptions({
|
|
620
|
+
clientId: _this.options.clientId,
|
|
621
|
+
host: _this.options.host,
|
|
622
|
+
redirectUri: "",
|
|
623
|
+
clientSecret: _this.options.clientSecret
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
signOut() {
|
|
627
|
+
this.clear();
|
|
628
|
+
}
|
|
629
|
+
clear() {
|
|
630
|
+
clearTokens();
|
|
631
|
+
clearOptions();
|
|
632
|
+
clearNonce();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
579
636
|
//#endregion
|
|
580
637
|
//#region src/http/errors.ts
|
|
581
638
|
/**
|
|
@@ -3041,6 +3098,157 @@ var Mapcache = class {
|
|
|
3041
3098
|
}
|
|
3042
3099
|
};
|
|
3043
3100
|
|
|
3101
|
+
//#endregion
|
|
3102
|
+
//#region src/ogc/Ogc.ts
|
|
3103
|
+
/** Default CRS of the OGC API: WGS 84 in longitude/latitude order. */
|
|
3104
|
+
const OGC_CRS84 = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
|
|
3105
|
+
/**
|
|
3106
|
+
* CRS URI for an EPSG code, e.g. `ogcEpsgCrs(25832)`. Note that
|
|
3107
|
+
* `ogcEpsgCrs(4326)` is latitude/longitude order; use `OGC_CRS84` for lon/lat.
|
|
3108
|
+
*/
|
|
3109
|
+
function ogcEpsgCrs(epsg) {
|
|
3110
|
+
return `http://www.opengis.net/def/crs/EPSG/0/${epsg}`;
|
|
3111
|
+
}
|
|
3112
|
+
function bboxToString(bbox) {
|
|
3113
|
+
return typeof bbox === "string" ? bbox : bbox.join(",");
|
|
3114
|
+
}
|
|
3115
|
+
function spatialQuery(options) {
|
|
3116
|
+
const query = {};
|
|
3117
|
+
if ((options === null || options === void 0 ? void 0 : options.bbox) !== void 0) query.bbox = bboxToString(options.bbox);
|
|
3118
|
+
if ((options === null || options === void 0 ? void 0 : options.bboxCrs) !== void 0) query["bbox-crs"] = options.bboxCrs;
|
|
3119
|
+
if ((options === null || options === void 0 ? void 0 : options.crs) !== void 0) query.crs = options.crs;
|
|
3120
|
+
if ((options === null || options === void 0 ? void 0 : options.datetime) !== void 0) query.datetime = options.datetime;
|
|
3121
|
+
return query;
|
|
3122
|
+
}
|
|
3123
|
+
function nonEmpty(query) {
|
|
3124
|
+
return Object.keys(query).length > 0 ? query : void 0;
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* OGC API Features (Part 1 Core, Part 2 CRS) and OGC API Maps (Part 1 Core)
|
|
3128
|
+
* wrapper for `/api/v4/ogc/database/{database}`.
|
|
3129
|
+
*
|
|
3130
|
+
* The endpoints accept Bearer token, HTTP Basic and anonymous requests. A
|
|
3131
|
+
* Bearer token must match the `database` in the path. Anonymous callers read
|
|
3132
|
+
* layers below `Read/write`; a `Read/write` collection answers 401 (Basic
|
|
3133
|
+
* challenge) for anonymous callers and 403 for identities without privilege.
|
|
3134
|
+
* Geofence rules, versioning and workflow are applied server-side.
|
|
3135
|
+
*
|
|
3136
|
+
* Map images cannot be fetched through this client; use `mapUrl` /
|
|
3137
|
+
* `datasetMapUrl` to build image URLs for `<img>` tags or map libraries.
|
|
3138
|
+
*
|
|
3139
|
+
* ```ts
|
|
3140
|
+
* const ogc = new Ogc(createCentiaClient({ baseUrl, auth }));
|
|
3141
|
+
* const page = await ogc.getItems('my_database', 'my_schema.roads', { bbox: [9, 55, 10, 56], limit: 100 });
|
|
3142
|
+
* ```
|
|
3143
|
+
*/
|
|
3144
|
+
var Ogc = class {
|
|
3145
|
+
constructor(client) {
|
|
3146
|
+
this.client = client;
|
|
3147
|
+
}
|
|
3148
|
+
basePath(database) {
|
|
3149
|
+
return `api/v4/ogc/database/${encodeURIComponent(database)}`;
|
|
3150
|
+
}
|
|
3151
|
+
collectionPath(database, collectionId) {
|
|
3152
|
+
return `${this.basePath(database)}/collections/${encodeURIComponent(collectionId)}`;
|
|
3153
|
+
}
|
|
3154
|
+
/** The landing page with links to conformance, collections and the OpenAPI document. */
|
|
3155
|
+
async getLandingPage(database) {
|
|
3156
|
+
var _this = this;
|
|
3157
|
+
return _this.client.request({
|
|
3158
|
+
path: _this.basePath(database),
|
|
3159
|
+
method: "GET"
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
/** Conformance classes implemented by the server. */
|
|
3163
|
+
async getConformance(database) {
|
|
3164
|
+
var _this2 = this;
|
|
3165
|
+
return _this2.client.request({
|
|
3166
|
+
path: `${_this2.basePath(database)}/conformance`,
|
|
3167
|
+
method: "GET"
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
/** The collections (OWS-enabled layers) the caller may read, paged. */
|
|
3171
|
+
async getCollections(database, options) {
|
|
3172
|
+
var _this3 = this;
|
|
3173
|
+
const query = {};
|
|
3174
|
+
if ((options === null || options === void 0 ? void 0 : options.limit) !== void 0) query.limit = String(options.limit);
|
|
3175
|
+
if ((options === null || options === void 0 ? void 0 : options.offset) !== void 0) query.offset = String(options.offset);
|
|
3176
|
+
return _this3.client.request({
|
|
3177
|
+
path: `${_this3.basePath(database)}/collections`,
|
|
3178
|
+
method: "GET",
|
|
3179
|
+
query: nonEmpty(query)
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* One collection. Throws a `CentiaApiError` with status 404 (unknown),
|
|
3184
|
+
* 401 (anonymous caller, credentials required) or 403 (no privilege).
|
|
3185
|
+
*/
|
|
3186
|
+
async getCollection(database, collectionId) {
|
|
3187
|
+
var _this4 = this;
|
|
3188
|
+
return _this4.client.request({
|
|
3189
|
+
path: _this4.collectionPath(database, collectionId),
|
|
3190
|
+
method: "GET"
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Features of a collection as a GeoJSON FeatureCollection page. The default
|
|
3195
|
+
* page size is 10; follow `links` with `rel: 'next'` or pass `offset`.
|
|
3196
|
+
*/
|
|
3197
|
+
async getItems(database, collectionId, options) {
|
|
3198
|
+
var _this5 = this;
|
|
3199
|
+
const query = {};
|
|
3200
|
+
if ((options === null || options === void 0 ? void 0 : options.limit) !== void 0) query.limit = String(options.limit);
|
|
3201
|
+
if ((options === null || options === void 0 ? void 0 : options.offset) !== void 0) query.offset = String(options.offset);
|
|
3202
|
+
Object.assign(query, spatialQuery(options));
|
|
3203
|
+
return _this5.client.request({
|
|
3204
|
+
path: `${_this5.collectionPath(database, collectionId)}/items`,
|
|
3205
|
+
method: "GET",
|
|
3206
|
+
query: nonEmpty(query),
|
|
3207
|
+
accept: "application/geo+json"
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
/** One feature by primary key. Throws a 404 `CentiaApiError` when it does not exist. */
|
|
3211
|
+
async getItem(database, collectionId, featureId, options) {
|
|
3212
|
+
var _this6 = this;
|
|
3213
|
+
const query = {};
|
|
3214
|
+
if ((options === null || options === void 0 ? void 0 : options.crs) !== void 0) query.crs = options.crs;
|
|
3215
|
+
if ((options === null || options === void 0 ? void 0 : options.datetime) !== void 0) query.datetime = options.datetime;
|
|
3216
|
+
return _this6.client.request({
|
|
3217
|
+
path: `${_this6.collectionPath(database, collectionId)}/items/${encodeURIComponent(String(featureId))}`,
|
|
3218
|
+
method: "GET",
|
|
3219
|
+
query: nonEmpty(query),
|
|
3220
|
+
accept: "application/geo+json"
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* URL of a rendered map of one collection (PNG/JPEG), without fetching it.
|
|
3225
|
+
* Authorization travels in the `Authorization` header, so for protected
|
|
3226
|
+
* collections fetch the image with the header set (or use Basic auth).
|
|
3227
|
+
*/
|
|
3228
|
+
mapUrl(database, collectionId, options) {
|
|
3229
|
+
return this.buildUrl(`${this.collectionPath(database, collectionId)}/map`, this.mapQuery(options));
|
|
3230
|
+
}
|
|
3231
|
+
/** URL of a rendered map of several collections of the same schema. */
|
|
3232
|
+
datasetMapUrl(database, collections, options) {
|
|
3233
|
+
const query = _objectSpread2({ collections: collections.join(",") }, this.mapQuery(options));
|
|
3234
|
+
return this.buildUrl(`${this.basePath(database)}/map`, query);
|
|
3235
|
+
}
|
|
3236
|
+
mapQuery(options) {
|
|
3237
|
+
const query = spatialQuery(options);
|
|
3238
|
+
if ((options === null || options === void 0 ? void 0 : options.width) !== void 0) query.width = String(options.width);
|
|
3239
|
+
if ((options === null || options === void 0 ? void 0 : options.height) !== void 0) query.height = String(options.height);
|
|
3240
|
+
if ((options === null || options === void 0 ? void 0 : options.format) !== void 0) query.f = options.format;
|
|
3241
|
+
if ((options === null || options === void 0 ? void 0 : options.transparent) !== void 0) query.transparent = String(options.transparent);
|
|
3242
|
+
if ((options === null || options === void 0 ? void 0 : options.bgcolor) !== void 0) query.bgcolor = options.bgcolor;
|
|
3243
|
+
return query;
|
|
3244
|
+
}
|
|
3245
|
+
buildUrl(path, query) {
|
|
3246
|
+
let url = `${this.client.baseUrl}/${path}`;
|
|
3247
|
+
if (Object.keys(query).length > 0) url += `?${new URLSearchParams(query).toString()}`;
|
|
3248
|
+
return url;
|
|
3249
|
+
}
|
|
3250
|
+
};
|
|
3251
|
+
|
|
3044
3252
|
//#endregion
|
|
3045
3253
|
//#region src/keyvalue/Keyvalue.ts
|
|
3046
3254
|
/**
|
|
@@ -3305,10 +3513,13 @@ exports.Claims = Claims;
|
|
|
3305
3513
|
exports.CodeFlow = CodeFlow;
|
|
3306
3514
|
exports.Features = Features;
|
|
3307
3515
|
exports.Gql = Gql;
|
|
3516
|
+
exports.GuestFlow = GuestFlow;
|
|
3308
3517
|
exports.Keyvalue = Keyvalue;
|
|
3309
3518
|
exports.Mapcache = Mapcache;
|
|
3310
3519
|
exports.Meta = Meta;
|
|
3311
3520
|
exports.NotLoggedInError = NotLoggedInError;
|
|
3521
|
+
exports.OGC_CRS84 = OGC_CRS84;
|
|
3522
|
+
exports.Ogc = Ogc;
|
|
3312
3523
|
exports.Ows = Ows;
|
|
3313
3524
|
exports.PasswordFlow = PasswordFlow;
|
|
3314
3525
|
exports.Rpc = Rpc;
|
|
@@ -3327,4 +3538,5 @@ exports.createCentiaAdminClient = createCentiaAdminClient;
|
|
|
3327
3538
|
exports.createCentiaClient = createCentiaClient;
|
|
3328
3539
|
exports.createSqlBuilder = createSqlBuilder;
|
|
3329
3540
|
exports.createTokenProvider = createTokenProvider;
|
|
3330
|
-
exports.isCentiaApiError = isCentiaApiError;
|
|
3541
|
+
exports.isCentiaApiError = isCentiaApiError;
|
|
3542
|
+
exports.ogcEpsgCrs = ogcEpsgCrs;
|