@geolonia/geonicdb-sdk 0.17.0 → 0.18.1

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/geonicdb.d.ts CHANGED
@@ -204,8 +204,8 @@ export interface EntityEvent {
204
204
  servicePath: string;
205
205
  /** Entity ID */
206
206
  entityId: string;
207
- /** Entity type, compacted with the subscription `@context` (#2026) */
208
- entityType: string;
207
+ /** Entity type, compacted with the subscription `@context` (#2026). Multi-type is `string[]` (#2477 / Table 5.2.4). */
208
+ entityType: string | string[];
209
209
  /**
210
210
  * Entity attributes in NGSI-LD normalized representation (clause 4.5.2, #2026 / #2044).
211
211
  * Attribute names are compacted with the subscription `@context`; sub-attributes are inline;
@@ -410,6 +410,26 @@ declare class EventEmitter {
410
410
  * Copyright (c) 2026 Geolonia Inc.
411
411
  * Released under the MIT License. See src/sdk/LICENSE for details.
412
412
  */
413
+ /**
414
+ * Which request produced an error.
415
+ *
416
+ * `login()` performs two requests — `POST /auth/login` and then
417
+ * `POST /auth/dpop-bind` — and both surface the server's `detail` /
418
+ * `description` verbatim as an `AuthenticationError`. Without this, a rejected
419
+ * credential and a rejected DPoP bind are indistinguishable from the error
420
+ * alone, so callers (and anyone reading a bug report) cannot tell which step
421
+ * actually failed.
422
+ */
423
+ export interface RequestContext {
424
+ method: string;
425
+ /**
426
+ * The endpoint path, without the base URL — e.g. `/auth/dpop-bind`. When the
427
+ * base URL itself carries a path prefix (`https://host/broker`), the path on
428
+ * the wire includes that prefix; this field names the endpoint, not the full
429
+ * request path.
430
+ */
431
+ path: string;
432
+ }
413
433
  /**
414
434
  * Base error class for GeonicDB SDK.
415
435
  * All SDK errors extend this class, allowing `instanceof GeonicDBError` checks.
@@ -417,11 +437,18 @@ declare class EventEmitter {
417
437
  export declare class GeonicDBError extends Error {
418
438
  /** HTTP status code (if applicable) */
419
439
  readonly statusCode: number;
440
+ /** The request that failed, when the SDK knows it. */
441
+ readonly request?: RequestContext;
420
442
  constructor(message: string, statusCode?: number);
421
443
  }
422
- /** 401 — Authentication failed (invalid credentials, expired token). */
444
+ /**
445
+ * Authentication failed (invalid credentials, expired token, or an HTTP
446
+ * failure on an auth endpoint). Defaults to 401; when thrown from an HTTP
447
+ * response, pass the real status so callers see what the server returned
448
+ * (#2649). Subclasses override with 409 / 403 for control-flow errors.
449
+ */
423
450
  export declare class AuthenticationError extends GeonicDBError {
424
- constructor(message?: string);
451
+ constructor(message?: string, statusCode?: number);
425
452
  }
426
453
  /** 403 — Authorization denied (insufficient permissions). */
427
454
  export declare class AuthorizationError extends GeonicDBError {
package/geonicdb.iife.js CHANGED
@@ -296,13 +296,20 @@ var GeonicDBModule = (() => {
296
296
  super(message);
297
297
  /** HTTP status code (if applicable) */
298
298
  __publicField(this, "statusCode");
299
+ /** The request that failed, when the SDK knows it. */
300
+ __publicField(this, "request");
299
301
  this.name = "GeonicDBError";
300
302
  this.statusCode = statusCode;
301
303
  }
302
304
  };
305
+ function withRequest(error, request) {
306
+ error.request = request;
307
+ error.message = `[${request.method} ${request.path}] ${error.message}`;
308
+ return error;
309
+ }
303
310
  var AuthenticationError = class extends GeonicDBError {
304
- constructor(message = "Authentication failed") {
305
- super(message, 401);
311
+ constructor(message = "Authentication failed", statusCode = 401) {
312
+ super(message, statusCode);
306
313
  this.name = "AuthenticationError";
307
314
  }
308
315
  };
@@ -326,16 +333,14 @@ var GeonicDBModule = (() => {
326
333
  };
327
334
  var PasswordResetRequiredError = class extends AuthenticationError {
328
335
  constructor(message = "Password reset required: set a new password to complete login") {
329
- super(message);
336
+ super(message, 409);
330
337
  this.name = "PasswordResetRequiredError";
331
- this.statusCode = 409;
332
338
  }
333
339
  };
334
340
  var TemporaryPasswordExpiredError = class extends AuthenticationError {
335
341
  constructor(message = "Temporary password expired: ask an administrator to re-issue it") {
336
- super(message);
342
+ super(message, 403);
337
343
  this.name = "TemporaryPasswordExpiredError";
338
- this.statusCode = 403;
339
344
  }
340
345
  };
341
346
  var ValidationError = class extends GeonicDBError {
@@ -740,12 +745,21 @@ var GeonicDBModule = (() => {
740
745
  const message = e.detail || e.description || "Login failed: " + res.status;
741
746
  const code = e.error || e.type || e.title || "";
742
747
  if (res.status === 409 && code.includes("PasswordResetRequired")) {
743
- throw new PasswordResetRequiredError(message);
748
+ throw withRequest(new PasswordResetRequiredError(message), {
749
+ method: "POST",
750
+ path: "/auth/login"
751
+ });
744
752
  }
745
753
  if (res.status === 403 && code.includes("TemporaryPasswordExpired")) {
746
- throw new TemporaryPasswordExpiredError(message);
754
+ throw withRequest(new TemporaryPasswordExpiredError(message), {
755
+ method: "POST",
756
+ path: "/auth/login"
757
+ });
747
758
  }
748
- throw new AuthenticationError(message);
759
+ throw withRequest(new AuthenticationError(message, res.status), {
760
+ method: "POST",
761
+ path: "/auth/login"
762
+ });
749
763
  }
750
764
  const data = await res.json();
751
765
  this._anonymous = false;
@@ -848,8 +862,12 @@ var GeonicDBModule = (() => {
848
862
  }
849
863
  if (!res.ok) {
850
864
  const e = await res.json().catch(() => ({}));
851
- throw new AuthenticationError(
852
- e.detail || e.description || "DPoP bind failed: " + res.status
865
+ throw withRequest(
866
+ new AuthenticationError(
867
+ e.detail || e.description || "DPoP bind failed: " + res.status,
868
+ res.status
869
+ ),
870
+ { method: "POST", path: "/auth/dpop-bind" }
853
871
  );
854
872
  }
855
873
  return await res.json();
@@ -1021,7 +1039,7 @@ var GeonicDBModule = (() => {
1021
1039
  this._refreshToken = null;
1022
1040
  this._token = null;
1023
1041
  this._tokenPromise = null;
1024
- throw new AuthenticationError("Token refresh failed: " + res.status);
1042
+ throw new AuthenticationError("Token refresh failed: " + res.status, res.status);
1025
1043
  }
1026
1044
  const data = await res.json();
1027
1045
  this._token = data.accessToken;
@@ -1089,7 +1107,7 @@ var GeonicDBModule = (() => {
1089
1107
  body: JSON.stringify({ api_key: this._apiKey })
1090
1108
  });
1091
1109
  if (!nonceRes.ok)
1092
- throw new AuthenticationError("Nonce request failed: " + nonceRes.status);
1110
+ throw new AuthenticationError("Nonce request failed: " + nonceRes.status, nonceRes.status);
1093
1111
  const nonceData = await nonceRes.json();
1094
1112
  if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;
1095
1113
  const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);
@@ -1102,7 +1120,7 @@ var GeonicDBModule = (() => {
1102
1120
  });
1103
1121
  const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);
1104
1122
  if (!res.ok)
1105
- throw new AuthenticationError("Token request failed: " + res.status);
1123
+ throw new AuthenticationError("Token request failed: " + res.status, res.status);
1106
1124
  const newNonce = res.headers.get("DPoP-Nonce");
1107
1125
  if (newNonce) this._dpopNonce = newNonce;
1108
1126
  const data = await res.json();
@@ -1144,8 +1162,8 @@ var GeonicDBModule = (() => {
1144
1162
  return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);
1145
1163
  }
1146
1164
  }
1147
- throw new GeonicDBError(
1148
- "Token request failed: " + (errBody.error_description || errBody.error),
1165
+ throw new AuthenticationError(
1166
+ "Token request failed: " + (errBody.error_description || errBody.error || "400"),
1149
1167
  400
1150
1168
  );
1151
1169
  }
@@ -1325,6 +1343,7 @@ var GeonicDBModule = (() => {
1325
1343
  __publicField(this, "_emit");
1326
1344
  __publicField(this, "_ws", null);
1327
1345
  __publicField(this, "_wsEndpoint", null);
1346
+ __publicField(this, "_deploymentHostname", null);
1328
1347
  __publicField(this, "_wsIntentionalClose", false);
1329
1348
  __publicField(this, "_reconnectAttempts", 0);
1330
1349
  __publicField(this, "_tokenRefreshTimer", null);
@@ -1414,6 +1433,9 @@ var GeonicDBModule = (() => {
1414
1433
  const info = await res.json();
1415
1434
  if (info.enabled && info.endpoint) {
1416
1435
  this._wsEndpoint = info.endpoint;
1436
+ if (info.deployment) {
1437
+ this._deploymentHostname = info.deployment;
1438
+ }
1417
1439
  return this._wsEndpoint;
1418
1440
  }
1419
1441
  }
@@ -1425,7 +1447,11 @@ var GeonicDBModule = (() => {
1425
1447
  async _openWebSocket(token) {
1426
1448
  const endpoint = await this._discoverWsEndpoint();
1427
1449
  return new Promise((resolve, reject) => {
1428
- const wsUrl = endpoint + (endpoint.indexOf("?") === -1 ? "?" : "&") + "tenant=" + encodeURIComponent(this._tenant);
1450
+ const params = new URLSearchParams({ tenant: this._tenant });
1451
+ if (this._deploymentHostname) {
1452
+ params.set("deployment", this._deploymentHostname);
1453
+ }
1454
+ const wsUrl = endpoint + (endpoint.indexOf("?") === -1 ? "?" : "&") + params.toString();
1429
1455
  this._log("connecting", wsUrl);
1430
1456
  const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);
1431
1457
  this._ws = ws;
@@ -1724,6 +1750,7 @@ var GeonicDBModule = (() => {
1724
1750
  if (!tenant) tenant = script?.getAttribute?.("data-tenant") || "";
1725
1751
  if (!baseUrl) baseUrl = script?.getAttribute?.("data-base-url") || "";
1726
1752
  }
1753
+ baseUrl = baseUrl.replace(/\/+$/, "");
1727
1754
  this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug, opts.anonymous);
1728
1755
  this._auth.onTokenRefresh = (creds) => {
1729
1756
  this.onTokenRefresh?.(creds);