@geolonia/geonicdb-sdk 0.2.1 → 0.3.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/geonicdb.mjs CHANGED
@@ -148,12 +148,88 @@ var RECONNECT_BASE_MS = 1e3;
148
148
  var RECONNECT_MAX_DELAY_MS = 3e4;
149
149
  var SUB_PROTOCOL = "access_token";
150
150
 
151
+ // src/sdk/errors.ts
152
+ var GeonicDBError = class extends Error {
153
+ constructor(message, statusCode = 0) {
154
+ super(message);
155
+ /** HTTP status code (if applicable) */
156
+ __publicField(this, "statusCode");
157
+ this.name = "GeonicDBError";
158
+ this.statusCode = statusCode;
159
+ }
160
+ };
161
+ var AuthenticationError = class extends GeonicDBError {
162
+ constructor(message = "Authentication failed") {
163
+ super(message, 401);
164
+ this.name = "AuthenticationError";
165
+ }
166
+ };
167
+ var AuthorizationError = class extends GeonicDBError {
168
+ constructor(message = "Access denied") {
169
+ super(message, 403);
170
+ this.name = "AuthorizationError";
171
+ }
172
+ };
173
+ var NotFoundError = class extends GeonicDBError {
174
+ constructor(message = "Not found") {
175
+ super(message, 404);
176
+ this.name = "NotFoundError";
177
+ }
178
+ };
179
+ var ConflictError = class extends GeonicDBError {
180
+ constructor(message = "Conflict") {
181
+ super(message, 409);
182
+ this.name = "ConflictError";
183
+ }
184
+ };
185
+ var ValidationError = class extends GeonicDBError {
186
+ constructor(message = "Validation failed") {
187
+ super(message, 422);
188
+ this.name = "ValidationError";
189
+ }
190
+ };
191
+ var RateLimitError = class extends GeonicDBError {
192
+ constructor(message = "Rate limit exceeded", retryAfter = 1) {
193
+ super(message, 429);
194
+ /** Seconds to wait before retrying (from Retry-After header) */
195
+ __publicField(this, "retryAfter");
196
+ this.name = "RateLimitError";
197
+ this.retryAfter = retryAfter;
198
+ }
199
+ };
200
+ var NetworkError = class extends GeonicDBError {
201
+ constructor(message = "Network error") {
202
+ super(message, 0);
203
+ this.name = "NetworkError";
204
+ }
205
+ };
206
+ function createErrorFromResponse(status, body, fallbackMessage) {
207
+ const message = body.detail || body.description || fallbackMessage;
208
+ switch (status) {
209
+ case 401:
210
+ return new AuthenticationError(message);
211
+ case 403:
212
+ return new AuthorizationError(message);
213
+ case 404:
214
+ return new NotFoundError(message);
215
+ case 409:
216
+ return new ConflictError(message);
217
+ case 422:
218
+ return new ValidationError(message);
219
+ case 429:
220
+ return new RateLimitError(message);
221
+ default:
222
+ return new GeonicDBError(message, status);
223
+ }
224
+ }
225
+
151
226
  // src/sdk/auth.ts
152
227
  var _AuthManager = class _AuthManager {
153
- constructor(baseUrl, apiKey, tenant) {
228
+ constructor(baseUrl, apiKey, tenant, debug = false) {
154
229
  __publicField(this, "_baseUrl");
155
230
  __publicField(this, "_apiKey");
156
231
  __publicField(this, "_tenant");
232
+ __publicField(this, "_debug");
157
233
  __publicField(this, "_token", null);
158
234
  __publicField(this, "_tokenExpiry", 0);
159
235
  __publicField(this, "_tokenType", "Bearer");
@@ -167,6 +243,7 @@ var _AuthManager = class _AuthManager {
167
243
  this._baseUrl = baseUrl;
168
244
  this._apiKey = apiKey;
169
245
  this._tenant = tenant;
246
+ this._debug = debug;
170
247
  if (dpopSupported) {
171
248
  this._dpopReady = generateDPoPKeyPair().then((kp) => {
172
249
  this._dpopKeyPair = kp;
@@ -175,8 +252,12 @@ var _AuthManager = class _AuthManager {
175
252
  });
176
253
  }
177
254
  }
255
+ _log(...args) {
256
+ if (this._debug) console.log("[GeonicDB]", ...args);
257
+ }
178
258
  /** Login with email and password (Bearer JWT). */
179
259
  async login(email, password) {
260
+ this._log("login", email);
180
261
  const headers = {
181
262
  "Content-Type": "application/json"
182
263
  };
@@ -188,7 +269,7 @@ var _AuthManager = class _AuthManager {
188
269
  });
189
270
  if (!res.ok) {
190
271
  const e = await res.json().catch(() => ({}));
191
- throw new Error(
272
+ throw new AuthenticationError(
192
273
  e.detail || e.description || "Login failed: " + res.status
193
274
  );
194
275
  }
@@ -235,6 +316,7 @@ var _AuthManager = class _AuthManager {
235
316
  return this._tokenPromise;
236
317
  }
237
318
  async _refreshBearerToken() {
319
+ this._log("refreshing Bearer token");
238
320
  try {
239
321
  const res = await fetch(this._baseUrl + "/auth/refresh", {
240
322
  method: "POST",
@@ -245,7 +327,7 @@ var _AuthManager = class _AuthManager {
245
327
  this._refreshToken = null;
246
328
  this._token = null;
247
329
  this._tokenPromise = null;
248
- throw new Error("Token refresh failed: " + res.status);
330
+ throw new AuthenticationError("Token refresh failed: " + res.status);
249
331
  }
250
332
  const data = await res.json();
251
333
  this._token = data.accessToken;
@@ -274,7 +356,7 @@ var _AuthManager = class _AuthManager {
274
356
  body: JSON.stringify({ api_key: this._apiKey })
275
357
  });
276
358
  if (!nonceRes.ok)
277
- throw new Error("Nonce request failed: " + nonceRes.status);
359
+ throw new AuthenticationError("Nonce request failed: " + nonceRes.status);
278
360
  const nonceData = await nonceRes.json();
279
361
  if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;
280
362
  const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);
@@ -287,7 +369,7 @@ var _AuthManager = class _AuthManager {
287
369
  });
288
370
  const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);
289
371
  if (!res.ok)
290
- throw new Error("Token request failed: " + res.status);
372
+ throw new AuthenticationError("Token request failed: " + res.status);
291
373
  const newNonce = res.headers.get("DPoP-Nonce");
292
374
  if (newNonce) this._dpopNonce = newNonce;
293
375
  const data = await res.json();
@@ -327,8 +409,9 @@ var _AuthManager = class _AuthManager {
327
409
  return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);
328
410
  }
329
411
  }
330
- throw new Error(
331
- "Token request failed: " + (errBody.error_description || errBody.error)
412
+ throw new GeonicDBError(
413
+ "Token request failed: " + (errBody.error_description || errBody.error),
414
+ 400
332
415
  );
333
416
  }
334
417
  return res;
@@ -337,8 +420,11 @@ var _AuthManager = class _AuthManager {
337
420
  * Make an authenticated HTTP request with automatic token refresh and DPoP.
338
421
  */
339
422
  async request(method, path, body) {
423
+ this._log(method, path);
340
424
  const token = await this.ensureToken();
341
- return this._doAuthenticatedRequest(method, path, body, token);
425
+ const res = await this._doAuthenticatedRequest(method, path, body, token);
426
+ this._log(method, path, "\u2192", res.status);
427
+ return res;
342
428
  }
343
429
  async _doAuthenticatedRequest(method, path, body, token, retryCount = 0) {
344
430
  const url = this._baseUrl + path;
@@ -423,6 +509,9 @@ var WebSocketManager = class {
423
509
  this._emit = emit;
424
510
  this._wsEndpointOverride = wsEndpointOverride || null;
425
511
  }
512
+ _log(...args) {
513
+ if (this._auth._debug) console.log("[GeonicDB:WS]", ...args);
514
+ }
426
515
  /** Establish WebSocket connection (authentication is automatic). */
427
516
  async connect() {
428
517
  if (this._reconnectTimer) {
@@ -500,10 +589,12 @@ var WebSocketManager = class {
500
589
  const endpoint = await this._discoverWsEndpoint();
501
590
  return new Promise((resolve, reject) => {
502
591
  const wsUrl = endpoint + (endpoint.indexOf("?") === -1 ? "?" : "&") + "tenant=" + encodeURIComponent(this._tenant);
592
+ this._log("connecting", wsUrl);
503
593
  const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);
504
594
  this._ws = ws;
505
595
  ws.onopen = () => {
506
596
  if (this._ws !== ws) return;
597
+ this._log("connected");
507
598
  this._reconnectAttempts = 0;
508
599
  const isDPoP = this._auth._tokenType === "DPoP" && !!this._auth._dpopKeyPair;
509
600
  const bindPromise = isDPoP ? createDPoPProof(this._auth._dpopKeyPair, "GET", wsUrl, null).then(
@@ -548,6 +639,14 @@ var WebSocketManager = class {
548
639
  this._emit("error", new Error(msg.message));
549
640
  return;
550
641
  }
642
+ this._log("event", msg.type, msg.entityId || "");
643
+ if (msg.entityId && msg.data && typeof msg.data === "object") {
644
+ msg.entity = {
645
+ id: msg.entityId,
646
+ type: msg.entityType,
647
+ ...msg.data
648
+ };
649
+ }
551
650
  this._emit(msg.type, msg);
552
651
  this._emit("message", msg);
553
652
  };
@@ -650,7 +749,7 @@ var GeonicDB = class extends EventEmitter {
650
749
  if (!tenant) tenant = script?.getAttribute?.("data-tenant") || "";
651
750
  if (!baseUrl) baseUrl = script?.getAttribute?.("data-base-url") || "";
652
751
  }
653
- this._auth = new AuthManager(baseUrl, apiKey, tenant);
752
+ this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug);
654
753
  this._auth.onTokenRefresh = (creds) => {
655
754
  this.onTokenRefresh?.(creds);
656
755
  this.emit("tokenRefresh", creds);
@@ -687,9 +786,7 @@ var GeonicDB = class extends EventEmitter {
687
786
  const res = await this._auth.request("POST", "/ngsi-ld/v1/entities", entity);
688
787
  if (!res.ok) {
689
788
  const e = await res.json().catch(() => ({}));
690
- throw new Error(
691
- e.detail || e.description || "Create failed"
692
- );
789
+ throw createErrorFromResponse(res.status, e, "Create failed");
693
790
  }
694
791
  return { created: true };
695
792
  }
@@ -701,9 +798,7 @@ var GeonicDB = class extends EventEmitter {
701
798
  );
702
799
  if (!res.ok) {
703
800
  const e = await res.json().catch(() => ({}));
704
- throw new Error(
705
- e.detail || e.description || "Not found"
706
- );
801
+ throw createErrorFromResponse(res.status, e, "Not found");
707
802
  }
708
803
  return await res.json();
709
804
  }
@@ -724,9 +819,7 @@ var GeonicDB = class extends EventEmitter {
724
819
  );
725
820
  if (!res.ok) {
726
821
  const e = await res.json().catch(() => ({}));
727
- throw new Error(
728
- e.detail || e.description || "Query failed"
729
- );
822
+ throw createErrorFromResponse(res.status, e, "Query failed");
730
823
  }
731
824
  return await res.json();
732
825
  }
@@ -742,9 +835,7 @@ var GeonicDB = class extends EventEmitter {
742
835
  );
743
836
  if (!res.ok) {
744
837
  const e = await res.json().catch(() => ({}));
745
- throw new Error(
746
- e.detail || e.description || "Count failed"
747
- );
838
+ throw createErrorFromResponse(res.status, e, "Count failed");
748
839
  }
749
840
  const countHeader = res.headers.get("NGSILD-Results-Count");
750
841
  return countHeader ? parseInt(countHeader, 10) : 0;
@@ -758,9 +849,7 @@ var GeonicDB = class extends EventEmitter {
758
849
  );
759
850
  if (!res.ok) {
760
851
  const e = await res.json().catch(() => ({}));
761
- throw new Error(
762
- e.detail || e.description || "Update failed"
763
- );
852
+ throw createErrorFromResponse(res.status, e, "Update failed");
764
853
  }
765
854
  return { updated: true };
766
855
  }
@@ -772,9 +861,7 @@ var GeonicDB = class extends EventEmitter {
772
861
  );
773
862
  if (!res.ok) {
774
863
  const e = await res.json().catch(() => ({}));
775
- throw new Error(
776
- e.detail || e.description || "Delete failed"
777
- );
864
+ throw createErrorFromResponse(res.status, e, "Delete failed");
778
865
  }
779
866
  return { deleted: true };
780
867
  }
@@ -871,9 +958,7 @@ var GeonicDB = class extends EventEmitter {
871
958
  const res = await this._auth.request("GET", path);
872
959
  if (!res.ok) {
873
960
  const e = await res.json().catch(() => ({}));
874
- throw new Error(
875
- e.detail || e.description || fallbackError
876
- );
961
+ throw createErrorFromResponse(res.status, e, fallbackError);
877
962
  }
878
963
  return await res.json();
879
964
  }
@@ -882,9 +967,7 @@ var GeonicDB = class extends EventEmitter {
882
967
  const res = await this._auth.request("POST", path, body);
883
968
  if (!res.ok) {
884
969
  const e = await res.json().catch(() => ({}));
885
- throw new Error(
886
- e.detail || e.description || fallbackError
887
- );
970
+ throw createErrorFromResponse(res.status, e, fallbackError);
888
971
  }
889
972
  if (res.status === 204) return {};
890
973
  return await res.json();
@@ -898,9 +981,7 @@ var GeonicDB = class extends EventEmitter {
898
981
  const res = await this._auth.request(method, path, body);
899
982
  if (!res.ok) {
900
983
  const e = await res.json().catch(() => ({}));
901
- throw new Error(
902
- e.detail || e.description || "Request failed: " + res.status
903
- );
984
+ throw createErrorFromResponse(res.status, e, "Request failed: " + res.status);
904
985
  }
905
986
  const ct = res.headers.get("Content-Type") || "";
906
987
  if (res.status === 204 || !ct) return null;
@@ -941,7 +1022,15 @@ if (typeof window !== "undefined") {
941
1022
  window.GeonicDB = GeonicDB;
942
1023
  }
943
1024
  export {
1025
+ AuthenticationError,
1026
+ AuthorizationError,
1027
+ ConflictError,
944
1028
  GeonicDB,
1029
+ GeonicDBError,
1030
+ NetworkError,
1031
+ NotFoundError,
1032
+ RateLimitError,
1033
+ ValidationError,
945
1034
  index_default as default
946
1035
  };
947
1036
  //# sourceMappingURL=geonicdb.mjs.map