@geolonia/geonicdb-sdk 0.16.0 → 0.18.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/README.md +123 -12
- package/geonicdb.cjs +72 -25
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +145 -13
- package/geonicdb.iife.js +72 -25
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +72 -25
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +116 -11
package/geonicdb.mjs
CHANGED
|
@@ -260,13 +260,20 @@ var GeonicDBError = class extends Error {
|
|
|
260
260
|
super(message);
|
|
261
261
|
/** HTTP status code (if applicable) */
|
|
262
262
|
__publicField(this, "statusCode");
|
|
263
|
+
/** The request that failed, when the SDK knows it. */
|
|
264
|
+
__publicField(this, "request");
|
|
263
265
|
this.name = "GeonicDBError";
|
|
264
266
|
this.statusCode = statusCode;
|
|
265
267
|
}
|
|
266
268
|
};
|
|
269
|
+
function withRequest(error, request) {
|
|
270
|
+
error.request = request;
|
|
271
|
+
error.message = `[${request.method} ${request.path}] ${error.message}`;
|
|
272
|
+
return error;
|
|
273
|
+
}
|
|
267
274
|
var AuthenticationError = class extends GeonicDBError {
|
|
268
|
-
constructor(message = "Authentication failed") {
|
|
269
|
-
super(message,
|
|
275
|
+
constructor(message = "Authentication failed", statusCode = 401) {
|
|
276
|
+
super(message, statusCode);
|
|
270
277
|
this.name = "AuthenticationError";
|
|
271
278
|
}
|
|
272
279
|
};
|
|
@@ -290,16 +297,14 @@ var ConflictError = class extends GeonicDBError {
|
|
|
290
297
|
};
|
|
291
298
|
var PasswordResetRequiredError = class extends AuthenticationError {
|
|
292
299
|
constructor(message = "Password reset required: set a new password to complete login") {
|
|
293
|
-
super(message);
|
|
300
|
+
super(message, 409);
|
|
294
301
|
this.name = "PasswordResetRequiredError";
|
|
295
|
-
this.statusCode = 409;
|
|
296
302
|
}
|
|
297
303
|
};
|
|
298
304
|
var TemporaryPasswordExpiredError = class extends AuthenticationError {
|
|
299
305
|
constructor(message = "Temporary password expired: ask an administrator to re-issue it") {
|
|
300
|
-
super(message);
|
|
306
|
+
super(message, 403);
|
|
301
307
|
this.name = "TemporaryPasswordExpiredError";
|
|
302
|
-
this.statusCode = 403;
|
|
303
308
|
}
|
|
304
309
|
};
|
|
305
310
|
var ValidationError = class extends GeonicDBError {
|
|
@@ -704,12 +709,21 @@ var _AuthManager = class _AuthManager {
|
|
|
704
709
|
const message = e.detail || e.description || "Login failed: " + res.status;
|
|
705
710
|
const code = e.error || e.type || e.title || "";
|
|
706
711
|
if (res.status === 409 && code.includes("PasswordResetRequired")) {
|
|
707
|
-
throw new PasswordResetRequiredError(message)
|
|
712
|
+
throw withRequest(new PasswordResetRequiredError(message), {
|
|
713
|
+
method: "POST",
|
|
714
|
+
path: "/auth/login"
|
|
715
|
+
});
|
|
708
716
|
}
|
|
709
717
|
if (res.status === 403 && code.includes("TemporaryPasswordExpired")) {
|
|
710
|
-
throw new TemporaryPasswordExpiredError(message)
|
|
718
|
+
throw withRequest(new TemporaryPasswordExpiredError(message), {
|
|
719
|
+
method: "POST",
|
|
720
|
+
path: "/auth/login"
|
|
721
|
+
});
|
|
711
722
|
}
|
|
712
|
-
throw new AuthenticationError(message)
|
|
723
|
+
throw withRequest(new AuthenticationError(message, res.status), {
|
|
724
|
+
method: "POST",
|
|
725
|
+
path: "/auth/login"
|
|
726
|
+
});
|
|
713
727
|
}
|
|
714
728
|
const data = await res.json();
|
|
715
729
|
this._anonymous = false;
|
|
@@ -812,8 +826,12 @@ var _AuthManager = class _AuthManager {
|
|
|
812
826
|
}
|
|
813
827
|
if (!res.ok) {
|
|
814
828
|
const e = await res.json().catch(() => ({}));
|
|
815
|
-
throw
|
|
816
|
-
|
|
829
|
+
throw withRequest(
|
|
830
|
+
new AuthenticationError(
|
|
831
|
+
e.detail || e.description || "DPoP bind failed: " + res.status,
|
|
832
|
+
res.status
|
|
833
|
+
),
|
|
834
|
+
{ method: "POST", path: "/auth/dpop-bind" }
|
|
817
835
|
);
|
|
818
836
|
}
|
|
819
837
|
return await res.json();
|
|
@@ -985,7 +1003,7 @@ var _AuthManager = class _AuthManager {
|
|
|
985
1003
|
this._refreshToken = null;
|
|
986
1004
|
this._token = null;
|
|
987
1005
|
this._tokenPromise = null;
|
|
988
|
-
throw new AuthenticationError("Token refresh failed: " + res.status);
|
|
1006
|
+
throw new AuthenticationError("Token refresh failed: " + res.status, res.status);
|
|
989
1007
|
}
|
|
990
1008
|
const data = await res.json();
|
|
991
1009
|
this._token = data.accessToken;
|
|
@@ -1053,7 +1071,7 @@ var _AuthManager = class _AuthManager {
|
|
|
1053
1071
|
body: JSON.stringify({ api_key: this._apiKey })
|
|
1054
1072
|
});
|
|
1055
1073
|
if (!nonceRes.ok)
|
|
1056
|
-
throw new AuthenticationError("Nonce request failed: " + nonceRes.status);
|
|
1074
|
+
throw new AuthenticationError("Nonce request failed: " + nonceRes.status, nonceRes.status);
|
|
1057
1075
|
const nonceData = await nonceRes.json();
|
|
1058
1076
|
if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;
|
|
1059
1077
|
const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);
|
|
@@ -1066,7 +1084,7 @@ var _AuthManager = class _AuthManager {
|
|
|
1066
1084
|
});
|
|
1067
1085
|
const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);
|
|
1068
1086
|
if (!res.ok)
|
|
1069
|
-
throw new AuthenticationError("Token request failed: " + res.status);
|
|
1087
|
+
throw new AuthenticationError("Token request failed: " + res.status, res.status);
|
|
1070
1088
|
const newNonce = res.headers.get("DPoP-Nonce");
|
|
1071
1089
|
if (newNonce) this._dpopNonce = newNonce;
|
|
1072
1090
|
const data = await res.json();
|
|
@@ -1108,8 +1126,8 @@ var _AuthManager = class _AuthManager {
|
|
|
1108
1126
|
return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);
|
|
1109
1127
|
}
|
|
1110
1128
|
}
|
|
1111
|
-
throw new
|
|
1112
|
-
"Token request failed: " + (errBody.error_description || errBody.error),
|
|
1129
|
+
throw new AuthenticationError(
|
|
1130
|
+
"Token request failed: " + (errBody.error_description || errBody.error || "400"),
|
|
1113
1131
|
400
|
|
1114
1132
|
);
|
|
1115
1133
|
}
|
|
@@ -1210,13 +1228,12 @@ var _AuthManager = class _AuthManager {
|
|
|
1210
1228
|
}
|
|
1211
1229
|
const url = this._baseUrl + path;
|
|
1212
1230
|
const isDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && token !== null;
|
|
1213
|
-
const
|
|
1231
|
+
const pathname = path.split("?")[0];
|
|
1232
|
+
const isNgsiLdWrite = pathname.startsWith("/ngsi-ld/v1/") && pathname !== "/ngsi-ld/v1/jsonldContexts";
|
|
1233
|
+
const withCoreContext = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !("@context" in value) ? { ...value, "@context": NGSI_LD_CORE_CONTEXT } : value;
|
|
1214
1234
|
let effectiveBody = body;
|
|
1215
|
-
if (
|
|
1216
|
-
effectiveBody =
|
|
1217
|
-
...effectiveBody,
|
|
1218
|
-
"@context": NGSI_LD_CORE_CONTEXT
|
|
1219
|
-
};
|
|
1235
|
+
if (isNgsiLdWrite && effectiveBody !== void 0) {
|
|
1236
|
+
effectiveBody = Array.isArray(effectiveBody) ? effectiveBody.map(withCoreContext) : withCoreContext(effectiveBody);
|
|
1220
1237
|
}
|
|
1221
1238
|
const bodyStr = effectiveBody !== void 0 ? JSON.stringify(effectiveBody) : void 0;
|
|
1222
1239
|
const doRequest = async (reqToken, dpopNonce) => {
|
|
@@ -1290,6 +1307,7 @@ var WebSocketManager = class {
|
|
|
1290
1307
|
__publicField(this, "_emit");
|
|
1291
1308
|
__publicField(this, "_ws", null);
|
|
1292
1309
|
__publicField(this, "_wsEndpoint", null);
|
|
1310
|
+
__publicField(this, "_deploymentHostname", null);
|
|
1293
1311
|
__publicField(this, "_wsIntentionalClose", false);
|
|
1294
1312
|
__publicField(this, "_reconnectAttempts", 0);
|
|
1295
1313
|
__publicField(this, "_tokenRefreshTimer", null);
|
|
@@ -1362,6 +1380,7 @@ var WebSocketManager = class {
|
|
|
1362
1380
|
if (options.entityTypes) msg.entityTypes = options.entityTypes;
|
|
1363
1381
|
if (options.idPattern) msg.idPattern = options.idPattern;
|
|
1364
1382
|
if (options.scopeQ) msg.scopeQ = options.scopeQ;
|
|
1383
|
+
if (options["@context"]) msg["@context"] = options["@context"];
|
|
1365
1384
|
}
|
|
1366
1385
|
this._subscription = msg;
|
|
1367
1386
|
if (this._pendingSubscription) return;
|
|
@@ -1378,6 +1397,9 @@ var WebSocketManager = class {
|
|
|
1378
1397
|
const info = await res.json();
|
|
1379
1398
|
if (info.enabled && info.endpoint) {
|
|
1380
1399
|
this._wsEndpoint = info.endpoint;
|
|
1400
|
+
if (info.deployment) {
|
|
1401
|
+
this._deploymentHostname = info.deployment;
|
|
1402
|
+
}
|
|
1381
1403
|
return this._wsEndpoint;
|
|
1382
1404
|
}
|
|
1383
1405
|
}
|
|
@@ -1389,7 +1411,11 @@ var WebSocketManager = class {
|
|
|
1389
1411
|
async _openWebSocket(token) {
|
|
1390
1412
|
const endpoint = await this._discoverWsEndpoint();
|
|
1391
1413
|
return new Promise((resolve, reject) => {
|
|
1392
|
-
const
|
|
1414
|
+
const params = new URLSearchParams({ tenant: this._tenant });
|
|
1415
|
+
if (this._deploymentHostname) {
|
|
1416
|
+
params.set("deployment", this._deploymentHostname);
|
|
1417
|
+
}
|
|
1418
|
+
const wsUrl = endpoint + (endpoint.indexOf("?") === -1 ? "?" : "&") + params.toString();
|
|
1393
1419
|
this._log("connecting", wsUrl);
|
|
1394
1420
|
const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);
|
|
1395
1421
|
this._ws = ws;
|
|
@@ -1429,6 +1455,10 @@ var WebSocketManager = class {
|
|
|
1429
1455
|
return;
|
|
1430
1456
|
}
|
|
1431
1457
|
if (msg.type === "pong") return;
|
|
1458
|
+
if (msg.type === "subscribed") {
|
|
1459
|
+
this._emit("subscribed", void 0);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1432
1462
|
if (msg.type === "dpop_verified") {
|
|
1433
1463
|
this._pendingSubscription = false;
|
|
1434
1464
|
if (this._subscription) {
|
|
@@ -1684,6 +1714,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1684
1714
|
if (!tenant) tenant = script?.getAttribute?.("data-tenant") || "";
|
|
1685
1715
|
if (!baseUrl) baseUrl = script?.getAttribute?.("data-base-url") || "";
|
|
1686
1716
|
}
|
|
1717
|
+
baseUrl = baseUrl.replace(/\/+$/, "");
|
|
1687
1718
|
this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug, opts.anonymous);
|
|
1688
1719
|
this._auth.onTokenRefresh = (creds) => {
|
|
1689
1720
|
this.onTokenRefresh?.(creds);
|
|
@@ -1895,7 +1926,9 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1895
1926
|
limit: 0,
|
|
1896
1927
|
type: params?.type,
|
|
1897
1928
|
q: params?.q,
|
|
1898
|
-
scopeQ: params?.scopeQ
|
|
1929
|
+
scopeQ: params?.scopeQ,
|
|
1930
|
+
// #2278: clause 5.7.2.4 の "too wide query" 免除条件 (e) local scope。
|
|
1931
|
+
local: params?.local
|
|
1899
1932
|
});
|
|
1900
1933
|
const res = await this._auth.request("GET", path);
|
|
1901
1934
|
if (!res.ok) {
|
|
@@ -1992,7 +2025,21 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1992
2025
|
"Batch create failed"
|
|
1993
2026
|
);
|
|
1994
2027
|
}
|
|
1995
|
-
/**
|
|
2028
|
+
/**
|
|
2029
|
+
* Upsert multiple entities in a single request.
|
|
2030
|
+
*
|
|
2031
|
+
* #1742: the server follows ETSI GS CIM 009 clause 5.6.8.5 / Table 6.15.3.1-1:
|
|
2032
|
+
* - **201** with a `string[]` of the **created** entity IDs, when at least one entity
|
|
2033
|
+
* was newly created and none failed (merely updated entities are not listed);
|
|
2034
|
+
* - **204** with no body, when every entity already existed and was updated
|
|
2035
|
+
* (`_jsonPost` maps this to `{}`);
|
|
2036
|
+
* - **207** with a `BatchOperationResult` (`{ created, updated, errors }`), on partial
|
|
2037
|
+
* or total failure.
|
|
2038
|
+
*
|
|
2039
|
+
* The return type reflects all three shapes. Narrow with `Array.isArray(result)` to
|
|
2040
|
+
* detect the "something was created" case.
|
|
2041
|
+
* https://cim.etsi.org/NGSI-LD/official/clause-6.html
|
|
2042
|
+
*/
|
|
1996
2043
|
async batchUpsert(entities) {
|
|
1997
2044
|
return this._jsonPost(
|
|
1998
2045
|
"/ngsi-ld/v1/entityOperations/upsert",
|