@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/README.md +36 -1
- package/geonicdb.cjs +125 -36
- package/geonicdb.cjs.map +3 -3
- package/geonicdb.iife.js +125 -36
- package/geonicdb.iife.js.map +3 -3
- package/geonicdb.mjs +125 -36
- package/geonicdb.mjs.map +3 -3
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|