@geolonia/geonicdb-sdk 0.2.0 → 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/LICENSE +21 -0
- package/README.md +161 -15
- 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 +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Geolonia Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -47,19 +47,6 @@ await db.createEntity({
|
|
|
47
47
|
</script>
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
### Browser (direct distribution from GeonicDB)
|
|
51
|
-
|
|
52
|
-
```html
|
|
53
|
-
<script src="https://your-geonicdb.example.com/sdk/v1/geonicdb.js"
|
|
54
|
-
data-api-key="your-api-key"
|
|
55
|
-
data-tenant="your-tenant"
|
|
56
|
-
data-base-url="https://your-geonicdb.example.com"></script>
|
|
57
|
-
<script>
|
|
58
|
-
var db = new GeonicDB(); // auto-initialized from data-* attributes
|
|
59
|
-
db.getEntities({ type: 'Room' }).then(console.log);
|
|
60
|
-
</script>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
50
|
## Authentication
|
|
64
51
|
|
|
65
52
|
### API Key (DPoP + Proof of Work)
|
|
@@ -166,7 +153,8 @@ const result = await db.request('GET', '/ngsi-ld/v1/subscriptions');
|
|
|
166
153
|
db.subscribe({ entityTypes: ['Room'] });
|
|
167
154
|
|
|
168
155
|
db.on('entityUpdated', (event) => {
|
|
169
|
-
|
|
156
|
+
// event.entity contains the complete NGSI-LD entity ({ id, type, ...attributes })
|
|
157
|
+
console.log(`${event.entityId} updated:`, event.entity);
|
|
170
158
|
});
|
|
171
159
|
|
|
172
160
|
db.on('error', (err) => console.error(err));
|
|
@@ -189,6 +177,164 @@ await db.connect();
|
|
|
189
177
|
| `error` | `Error` | Error occurred |
|
|
190
178
|
| `tokenRefresh` | `RefreshedCredentials` | Bearer token was refreshed |
|
|
191
179
|
|
|
180
|
+
## API Reference
|
|
181
|
+
|
|
182
|
+
### Constructor
|
|
183
|
+
|
|
184
|
+
| Method | Parameters | Returns | Description |
|
|
185
|
+
|--------|-----------|---------|-------------|
|
|
186
|
+
| `new GeonicDB(options?)` | `GeonicDBOptions` | `GeonicDB` | Create a new SDK instance |
|
|
187
|
+
|
|
188
|
+
#### `GeonicDBOptions`
|
|
189
|
+
|
|
190
|
+
| Property | Type | Required | Description |
|
|
191
|
+
|----------|------|----------|-------------|
|
|
192
|
+
| `apiKey` | `string` | No | API key (or use `data-api-key` attribute on script tag) |
|
|
193
|
+
| `tenant` | `string` | No | Tenant name (or use `data-tenant` attribute on script tag) |
|
|
194
|
+
| `baseUrl` | `string` | No | API base URL (auto-detected from script `src` if omitted) |
|
|
195
|
+
| `wsEndpoint` | `string` | No | WebSocket endpoint URL (auto-detected from `baseUrl` if omitted) |
|
|
196
|
+
| `debug` | `boolean` | No | Enable debug logging to console (default: `false`) |
|
|
197
|
+
|
|
198
|
+
### Authentication
|
|
199
|
+
|
|
200
|
+
| Method | Parameters | Returns | Description |
|
|
201
|
+
|--------|-----------|---------|-------------|
|
|
202
|
+
| `login(email, password)` | `string, string` | `Promise<LoginResponse>` | Login with email and password (Bearer JWT) |
|
|
203
|
+
| `setCredentials(opts)` | `CredentialsOptions` | `this` | Set credentials externally (e.g. from a login API response) |
|
|
204
|
+
| `logout()` | — | `void` | Clear all credentials and disconnect WebSocket |
|
|
205
|
+
|
|
206
|
+
### Entity CRUD
|
|
207
|
+
|
|
208
|
+
| Method | Parameters | Returns | Description |
|
|
209
|
+
|--------|-----------|---------|-------------|
|
|
210
|
+
| `createEntity(entity)` | `Record<string, unknown>` | `Promise<{ created: true }>` | Create a new entity |
|
|
211
|
+
| `getEntity(entityId)` | `string` | `Promise<Record<string, unknown>>` | Get a single entity by ID |
|
|
212
|
+
| `getEntities(params?)` | `GetEntitiesParams` | `Promise<Record<string, unknown>[]>` | Query entities with optional filters |
|
|
213
|
+
| `count(params?)` | `CountEntitiesParams` | `Promise<number>` | Count entities matching the given filters |
|
|
214
|
+
| `updateEntity(entityId, attrs)` | `string, Record<string, unknown>` | `Promise<{ updated: true }>` | Update entity attributes (partial) |
|
|
215
|
+
| `deleteEntity(entityId)` | `string` | `Promise<{ deleted: true }>` | Delete an entity |
|
|
216
|
+
|
|
217
|
+
#### `GetEntitiesParams`
|
|
218
|
+
|
|
219
|
+
| Property | Type | Description |
|
|
220
|
+
|----------|------|-------------|
|
|
221
|
+
| `type` | `string` | Entity type filter |
|
|
222
|
+
| `limit` | `number` | Max results |
|
|
223
|
+
| `offset` | `number` | Pagination offset |
|
|
224
|
+
| `q` | `string` | NGSI-LD query expression |
|
|
225
|
+
|
|
226
|
+
#### `CountEntitiesParams`
|
|
227
|
+
|
|
228
|
+
| Property | Type | Description |
|
|
229
|
+
|----------|------|-------------|
|
|
230
|
+
| `type` | `string` | Entity type filter |
|
|
231
|
+
| `q` | `string` | NGSI-LD query expression |
|
|
232
|
+
|
|
233
|
+
### Types / Attributes Discovery
|
|
234
|
+
|
|
235
|
+
| Method | Parameters | Returns | Description |
|
|
236
|
+
|--------|-----------|---------|-------------|
|
|
237
|
+
| `getTypes()` | — | `Promise<Record<string, unknown>[]>` | List all entity types |
|
|
238
|
+
| `getType(typeName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific entity type |
|
|
239
|
+
| `getAttributes()` | — | `Promise<Record<string, unknown>[]>` | List all attributes |
|
|
240
|
+
| `getAttribute(attrName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific attribute |
|
|
241
|
+
|
|
242
|
+
### Temporal API
|
|
243
|
+
|
|
244
|
+
| Method | Parameters | Returns | Description |
|
|
245
|
+
|--------|-----------|---------|-------------|
|
|
246
|
+
| `getTemporalEntities(params?)` | `GetTemporalEntitiesParams` | `Promise<Record<string, unknown>[]>` | Query temporal entities |
|
|
247
|
+
| `getTemporalEntity(entityId)` | `string` | `Promise<Record<string, unknown>>` | Get temporal representation of a single entity |
|
|
248
|
+
|
|
249
|
+
#### `GetTemporalEntitiesParams`
|
|
250
|
+
|
|
251
|
+
| Property | Type | Description |
|
|
252
|
+
|----------|------|-------------|
|
|
253
|
+
| `type` | `string` | Entity type filter |
|
|
254
|
+
| `id` | `string` | Entity ID |
|
|
255
|
+
| `idPattern` | `string` | Entity ID pattern (regex) |
|
|
256
|
+
| `attrs` | `string` | Attribute names to retrieve |
|
|
257
|
+
| `q` | `string` | NGSI-LD query expression |
|
|
258
|
+
| `timeproperty` | `string` | Time property (`observedAt`, `createdAt`, `modifiedAt`) |
|
|
259
|
+
| `timeAt` | `string` | Start time (ISO 8601) |
|
|
260
|
+
| `endTimeAt` | `string` | End time (ISO 8601) |
|
|
261
|
+
| `timerel` | `string` | Temporal relation (`before`, `after`, `between`) |
|
|
262
|
+
| `limit` | `number` | Max results |
|
|
263
|
+
| `offset` | `number` | Pagination offset |
|
|
264
|
+
|
|
265
|
+
### Batch Operations
|
|
266
|
+
|
|
267
|
+
| Method | Parameters | Returns | Description |
|
|
268
|
+
|--------|-----------|---------|-------------|
|
|
269
|
+
| `batchCreate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Create multiple entities |
|
|
270
|
+
| `batchUpsert(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Upsert multiple entities |
|
|
271
|
+
| `batchUpdate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Update multiple entities |
|
|
272
|
+
| `batchDelete(entityIds)` | `string[]` | `Promise<Record<string, unknown>>` | Delete multiple entities by ID |
|
|
273
|
+
|
|
274
|
+
### Generic Request
|
|
275
|
+
|
|
276
|
+
| Method | Parameters | Returns | Description |
|
|
277
|
+
|--------|-----------|---------|-------------|
|
|
278
|
+
| `request(method, path, body?)` | `string, string, unknown` | `Promise<unknown>` | Authenticated request with automatic JSON parsing |
|
|
279
|
+
| `requestRaw(method, path, body?)` | `string, string, unknown` | `Promise<Response>` | Authenticated request returning raw `Response` (for accessing headers) |
|
|
280
|
+
|
|
281
|
+
### WebSocket
|
|
282
|
+
|
|
283
|
+
| Method | Parameters | Returns | Description |
|
|
284
|
+
|--------|-----------|---------|-------------|
|
|
285
|
+
| `connect()` | — | `Promise<void>` | Establish WebSocket connection |
|
|
286
|
+
| `reconnect()` | — | `Promise<void>` | Force-reconnect (preserves subscriptions) |
|
|
287
|
+
| `disconnect()` | — | `void` | Disconnect WebSocket |
|
|
288
|
+
| `isConnected()` | — | `boolean` | Check if WebSocket is currently open |
|
|
289
|
+
| `subscribe(options?)` | `SubscribeOptions` | `void` | Subscribe to entity events |
|
|
290
|
+
|
|
291
|
+
#### `SubscribeOptions`
|
|
292
|
+
|
|
293
|
+
| Property | Type | Description |
|
|
294
|
+
|----------|------|-------------|
|
|
295
|
+
| `entityTypes` | `string[]` | Entity types to subscribe to |
|
|
296
|
+
| `idPattern` | `string` | Entity ID regex pattern |
|
|
297
|
+
|
|
298
|
+
### Event Handling
|
|
299
|
+
|
|
300
|
+
| Method | Parameters | Returns | Description |
|
|
301
|
+
|--------|-----------|---------|-------------|
|
|
302
|
+
| `on(event, listener)` | `string, Function` | `this` | Register an event listener |
|
|
303
|
+
| `off(event, listener)` | `string, Function` | `this` | Remove an event listener |
|
|
304
|
+
|
|
305
|
+
## Error Handling
|
|
306
|
+
|
|
307
|
+
All async methods throw typed errors extending `GeonicDBError`. Use `instanceof` to distinguish error types.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { GeonicDBError, AuthenticationError, NotFoundError } from '@geolonia/geonicdb-sdk';
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
await db.getEntity('urn:ngsi-ld:Room:404');
|
|
314
|
+
} catch (err) {
|
|
315
|
+
if (err instanceof AuthenticationError) {
|
|
316
|
+
// 401 — re-login needed
|
|
317
|
+
} else if (err instanceof NotFoundError) {
|
|
318
|
+
// 404 — entity not found
|
|
319
|
+
} else if (err instanceof GeonicDBError) {
|
|
320
|
+
console.error(err.message, err.statusCode);
|
|
321
|
+
} else {
|
|
322
|
+
console.error(err);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
| Class | Status | Description |
|
|
328
|
+
|-------|--------|-------------|
|
|
329
|
+
| `GeonicDBError` | — | Base class (`statusCode` property) |
|
|
330
|
+
| `AuthenticationError` | 401 | Invalid credentials or expired token |
|
|
331
|
+
| `AuthorizationError` | 403 | Insufficient permissions |
|
|
332
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
333
|
+
| `ConflictError` | 409 | Entity already exists |
|
|
334
|
+
| `ValidationError` | 422 | Bad request payload |
|
|
335
|
+
| `RateLimitError` | 429 | Rate limited (`retryAfter` property) |
|
|
336
|
+
| `NetworkError` | — | Fetch failure, DNS error, timeout |
|
|
337
|
+
|
|
192
338
|
## License
|
|
193
339
|
|
|
194
|
-
|
|
340
|
+
MIT
|
package/geonicdb.cjs
CHANGED
|
@@ -22,7 +22,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
22
22
|
// src/sdk/index.ts
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
|
+
AuthenticationError: () => AuthenticationError,
|
|
26
|
+
AuthorizationError: () => AuthorizationError,
|
|
27
|
+
ConflictError: () => ConflictError,
|
|
25
28
|
GeonicDB: () => GeonicDB,
|
|
29
|
+
GeonicDBError: () => GeonicDBError,
|
|
30
|
+
NetworkError: () => NetworkError,
|
|
31
|
+
NotFoundError: () => NotFoundError,
|
|
32
|
+
RateLimitError: () => RateLimitError,
|
|
33
|
+
ValidationError: () => ValidationError,
|
|
26
34
|
default: () => index_default
|
|
27
35
|
});
|
|
28
36
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -173,12 +181,88 @@ var RECONNECT_BASE_MS = 1e3;
|
|
|
173
181
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
174
182
|
var SUB_PROTOCOL = "access_token";
|
|
175
183
|
|
|
184
|
+
// src/sdk/errors.ts
|
|
185
|
+
var GeonicDBError = class extends Error {
|
|
186
|
+
constructor(message, statusCode = 0) {
|
|
187
|
+
super(message);
|
|
188
|
+
/** HTTP status code (if applicable) */
|
|
189
|
+
__publicField(this, "statusCode");
|
|
190
|
+
this.name = "GeonicDBError";
|
|
191
|
+
this.statusCode = statusCode;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var AuthenticationError = class extends GeonicDBError {
|
|
195
|
+
constructor(message = "Authentication failed") {
|
|
196
|
+
super(message, 401);
|
|
197
|
+
this.name = "AuthenticationError";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var AuthorizationError = class extends GeonicDBError {
|
|
201
|
+
constructor(message = "Access denied") {
|
|
202
|
+
super(message, 403);
|
|
203
|
+
this.name = "AuthorizationError";
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var NotFoundError = class extends GeonicDBError {
|
|
207
|
+
constructor(message = "Not found") {
|
|
208
|
+
super(message, 404);
|
|
209
|
+
this.name = "NotFoundError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var ConflictError = class extends GeonicDBError {
|
|
213
|
+
constructor(message = "Conflict") {
|
|
214
|
+
super(message, 409);
|
|
215
|
+
this.name = "ConflictError";
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var ValidationError = class extends GeonicDBError {
|
|
219
|
+
constructor(message = "Validation failed") {
|
|
220
|
+
super(message, 422);
|
|
221
|
+
this.name = "ValidationError";
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var RateLimitError = class extends GeonicDBError {
|
|
225
|
+
constructor(message = "Rate limit exceeded", retryAfter = 1) {
|
|
226
|
+
super(message, 429);
|
|
227
|
+
/** Seconds to wait before retrying (from Retry-After header) */
|
|
228
|
+
__publicField(this, "retryAfter");
|
|
229
|
+
this.name = "RateLimitError";
|
|
230
|
+
this.retryAfter = retryAfter;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
var NetworkError = class extends GeonicDBError {
|
|
234
|
+
constructor(message = "Network error") {
|
|
235
|
+
super(message, 0);
|
|
236
|
+
this.name = "NetworkError";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function createErrorFromResponse(status, body, fallbackMessage) {
|
|
240
|
+
const message = body.detail || body.description || fallbackMessage;
|
|
241
|
+
switch (status) {
|
|
242
|
+
case 401:
|
|
243
|
+
return new AuthenticationError(message);
|
|
244
|
+
case 403:
|
|
245
|
+
return new AuthorizationError(message);
|
|
246
|
+
case 404:
|
|
247
|
+
return new NotFoundError(message);
|
|
248
|
+
case 409:
|
|
249
|
+
return new ConflictError(message);
|
|
250
|
+
case 422:
|
|
251
|
+
return new ValidationError(message);
|
|
252
|
+
case 429:
|
|
253
|
+
return new RateLimitError(message);
|
|
254
|
+
default:
|
|
255
|
+
return new GeonicDBError(message, status);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
176
259
|
// src/sdk/auth.ts
|
|
177
260
|
var _AuthManager = class _AuthManager {
|
|
178
|
-
constructor(baseUrl, apiKey, tenant) {
|
|
261
|
+
constructor(baseUrl, apiKey, tenant, debug = false) {
|
|
179
262
|
__publicField(this, "_baseUrl");
|
|
180
263
|
__publicField(this, "_apiKey");
|
|
181
264
|
__publicField(this, "_tenant");
|
|
265
|
+
__publicField(this, "_debug");
|
|
182
266
|
__publicField(this, "_token", null);
|
|
183
267
|
__publicField(this, "_tokenExpiry", 0);
|
|
184
268
|
__publicField(this, "_tokenType", "Bearer");
|
|
@@ -192,6 +276,7 @@ var _AuthManager = class _AuthManager {
|
|
|
192
276
|
this._baseUrl = baseUrl;
|
|
193
277
|
this._apiKey = apiKey;
|
|
194
278
|
this._tenant = tenant;
|
|
279
|
+
this._debug = debug;
|
|
195
280
|
if (dpopSupported) {
|
|
196
281
|
this._dpopReady = generateDPoPKeyPair().then((kp) => {
|
|
197
282
|
this._dpopKeyPair = kp;
|
|
@@ -200,8 +285,12 @@ var _AuthManager = class _AuthManager {
|
|
|
200
285
|
});
|
|
201
286
|
}
|
|
202
287
|
}
|
|
288
|
+
_log(...args) {
|
|
289
|
+
if (this._debug) console.log("[GeonicDB]", ...args);
|
|
290
|
+
}
|
|
203
291
|
/** Login with email and password (Bearer JWT). */
|
|
204
292
|
async login(email, password) {
|
|
293
|
+
this._log("login", email);
|
|
205
294
|
const headers = {
|
|
206
295
|
"Content-Type": "application/json"
|
|
207
296
|
};
|
|
@@ -213,7 +302,7 @@ var _AuthManager = class _AuthManager {
|
|
|
213
302
|
});
|
|
214
303
|
if (!res.ok) {
|
|
215
304
|
const e = await res.json().catch(() => ({}));
|
|
216
|
-
throw new
|
|
305
|
+
throw new AuthenticationError(
|
|
217
306
|
e.detail || e.description || "Login failed: " + res.status
|
|
218
307
|
);
|
|
219
308
|
}
|
|
@@ -260,6 +349,7 @@ var _AuthManager = class _AuthManager {
|
|
|
260
349
|
return this._tokenPromise;
|
|
261
350
|
}
|
|
262
351
|
async _refreshBearerToken() {
|
|
352
|
+
this._log("refreshing Bearer token");
|
|
263
353
|
try {
|
|
264
354
|
const res = await fetch(this._baseUrl + "/auth/refresh", {
|
|
265
355
|
method: "POST",
|
|
@@ -270,7 +360,7 @@ var _AuthManager = class _AuthManager {
|
|
|
270
360
|
this._refreshToken = null;
|
|
271
361
|
this._token = null;
|
|
272
362
|
this._tokenPromise = null;
|
|
273
|
-
throw new
|
|
363
|
+
throw new AuthenticationError("Token refresh failed: " + res.status);
|
|
274
364
|
}
|
|
275
365
|
const data = await res.json();
|
|
276
366
|
this._token = data.accessToken;
|
|
@@ -299,7 +389,7 @@ var _AuthManager = class _AuthManager {
|
|
|
299
389
|
body: JSON.stringify({ api_key: this._apiKey })
|
|
300
390
|
});
|
|
301
391
|
if (!nonceRes.ok)
|
|
302
|
-
throw new
|
|
392
|
+
throw new AuthenticationError("Nonce request failed: " + nonceRes.status);
|
|
303
393
|
const nonceData = await nonceRes.json();
|
|
304
394
|
if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;
|
|
305
395
|
const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);
|
|
@@ -312,7 +402,7 @@ var _AuthManager = class _AuthManager {
|
|
|
312
402
|
});
|
|
313
403
|
const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);
|
|
314
404
|
if (!res.ok)
|
|
315
|
-
throw new
|
|
405
|
+
throw new AuthenticationError("Token request failed: " + res.status);
|
|
316
406
|
const newNonce = res.headers.get("DPoP-Nonce");
|
|
317
407
|
if (newNonce) this._dpopNonce = newNonce;
|
|
318
408
|
const data = await res.json();
|
|
@@ -352,8 +442,9 @@ var _AuthManager = class _AuthManager {
|
|
|
352
442
|
return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);
|
|
353
443
|
}
|
|
354
444
|
}
|
|
355
|
-
throw new
|
|
356
|
-
"Token request failed: " + (errBody.error_description || errBody.error)
|
|
445
|
+
throw new GeonicDBError(
|
|
446
|
+
"Token request failed: " + (errBody.error_description || errBody.error),
|
|
447
|
+
400
|
|
357
448
|
);
|
|
358
449
|
}
|
|
359
450
|
return res;
|
|
@@ -362,8 +453,11 @@ var _AuthManager = class _AuthManager {
|
|
|
362
453
|
* Make an authenticated HTTP request with automatic token refresh and DPoP.
|
|
363
454
|
*/
|
|
364
455
|
async request(method, path, body) {
|
|
456
|
+
this._log(method, path);
|
|
365
457
|
const token = await this.ensureToken();
|
|
366
|
-
|
|
458
|
+
const res = await this._doAuthenticatedRequest(method, path, body, token);
|
|
459
|
+
this._log(method, path, "\u2192", res.status);
|
|
460
|
+
return res;
|
|
367
461
|
}
|
|
368
462
|
async _doAuthenticatedRequest(method, path, body, token, retryCount = 0) {
|
|
369
463
|
const url = this._baseUrl + path;
|
|
@@ -448,6 +542,9 @@ var WebSocketManager = class {
|
|
|
448
542
|
this._emit = emit;
|
|
449
543
|
this._wsEndpointOverride = wsEndpointOverride || null;
|
|
450
544
|
}
|
|
545
|
+
_log(...args) {
|
|
546
|
+
if (this._auth._debug) console.log("[GeonicDB:WS]", ...args);
|
|
547
|
+
}
|
|
451
548
|
/** Establish WebSocket connection (authentication is automatic). */
|
|
452
549
|
async connect() {
|
|
453
550
|
if (this._reconnectTimer) {
|
|
@@ -525,10 +622,12 @@ var WebSocketManager = class {
|
|
|
525
622
|
const endpoint = await this._discoverWsEndpoint();
|
|
526
623
|
return new Promise((resolve, reject) => {
|
|
527
624
|
const wsUrl = endpoint + (endpoint.indexOf("?") === -1 ? "?" : "&") + "tenant=" + encodeURIComponent(this._tenant);
|
|
625
|
+
this._log("connecting", wsUrl);
|
|
528
626
|
const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);
|
|
529
627
|
this._ws = ws;
|
|
530
628
|
ws.onopen = () => {
|
|
531
629
|
if (this._ws !== ws) return;
|
|
630
|
+
this._log("connected");
|
|
532
631
|
this._reconnectAttempts = 0;
|
|
533
632
|
const isDPoP = this._auth._tokenType === "DPoP" && !!this._auth._dpopKeyPair;
|
|
534
633
|
const bindPromise = isDPoP ? createDPoPProof(this._auth._dpopKeyPair, "GET", wsUrl, null).then(
|
|
@@ -573,6 +672,14 @@ var WebSocketManager = class {
|
|
|
573
672
|
this._emit("error", new Error(msg.message));
|
|
574
673
|
return;
|
|
575
674
|
}
|
|
675
|
+
this._log("event", msg.type, msg.entityId || "");
|
|
676
|
+
if (msg.entityId && msg.data && typeof msg.data === "object") {
|
|
677
|
+
msg.entity = {
|
|
678
|
+
id: msg.entityId,
|
|
679
|
+
type: msg.entityType,
|
|
680
|
+
...msg.data
|
|
681
|
+
};
|
|
682
|
+
}
|
|
576
683
|
this._emit(msg.type, msg);
|
|
577
684
|
this._emit("message", msg);
|
|
578
685
|
};
|
|
@@ -675,7 +782,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
675
782
|
if (!tenant) tenant = script?.getAttribute?.("data-tenant") || "";
|
|
676
783
|
if (!baseUrl) baseUrl = script?.getAttribute?.("data-base-url") || "";
|
|
677
784
|
}
|
|
678
|
-
this._auth = new AuthManager(baseUrl, apiKey, tenant);
|
|
785
|
+
this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug);
|
|
679
786
|
this._auth.onTokenRefresh = (creds) => {
|
|
680
787
|
this.onTokenRefresh?.(creds);
|
|
681
788
|
this.emit("tokenRefresh", creds);
|
|
@@ -712,9 +819,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
712
819
|
const res = await this._auth.request("POST", "/ngsi-ld/v1/entities", entity);
|
|
713
820
|
if (!res.ok) {
|
|
714
821
|
const e = await res.json().catch(() => ({}));
|
|
715
|
-
throw
|
|
716
|
-
e.detail || e.description || "Create failed"
|
|
717
|
-
);
|
|
822
|
+
throw createErrorFromResponse(res.status, e, "Create failed");
|
|
718
823
|
}
|
|
719
824
|
return { created: true };
|
|
720
825
|
}
|
|
@@ -726,9 +831,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
726
831
|
);
|
|
727
832
|
if (!res.ok) {
|
|
728
833
|
const e = await res.json().catch(() => ({}));
|
|
729
|
-
throw
|
|
730
|
-
e.detail || e.description || "Not found"
|
|
731
|
-
);
|
|
834
|
+
throw createErrorFromResponse(res.status, e, "Not found");
|
|
732
835
|
}
|
|
733
836
|
return await res.json();
|
|
734
837
|
}
|
|
@@ -749,9 +852,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
749
852
|
);
|
|
750
853
|
if (!res.ok) {
|
|
751
854
|
const e = await res.json().catch(() => ({}));
|
|
752
|
-
throw
|
|
753
|
-
e.detail || e.description || "Query failed"
|
|
754
|
-
);
|
|
855
|
+
throw createErrorFromResponse(res.status, e, "Query failed");
|
|
755
856
|
}
|
|
756
857
|
return await res.json();
|
|
757
858
|
}
|
|
@@ -767,9 +868,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
767
868
|
);
|
|
768
869
|
if (!res.ok) {
|
|
769
870
|
const e = await res.json().catch(() => ({}));
|
|
770
|
-
throw
|
|
771
|
-
e.detail || e.description || "Count failed"
|
|
772
|
-
);
|
|
871
|
+
throw createErrorFromResponse(res.status, e, "Count failed");
|
|
773
872
|
}
|
|
774
873
|
const countHeader = res.headers.get("NGSILD-Results-Count");
|
|
775
874
|
return countHeader ? parseInt(countHeader, 10) : 0;
|
|
@@ -783,9 +882,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
783
882
|
);
|
|
784
883
|
if (!res.ok) {
|
|
785
884
|
const e = await res.json().catch(() => ({}));
|
|
786
|
-
throw
|
|
787
|
-
e.detail || e.description || "Update failed"
|
|
788
|
-
);
|
|
885
|
+
throw createErrorFromResponse(res.status, e, "Update failed");
|
|
789
886
|
}
|
|
790
887
|
return { updated: true };
|
|
791
888
|
}
|
|
@@ -797,9 +894,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
797
894
|
);
|
|
798
895
|
if (!res.ok) {
|
|
799
896
|
const e = await res.json().catch(() => ({}));
|
|
800
|
-
throw
|
|
801
|
-
e.detail || e.description || "Delete failed"
|
|
802
|
-
);
|
|
897
|
+
throw createErrorFromResponse(res.status, e, "Delete failed");
|
|
803
898
|
}
|
|
804
899
|
return { deleted: true };
|
|
805
900
|
}
|
|
@@ -896,9 +991,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
896
991
|
const res = await this._auth.request("GET", path);
|
|
897
992
|
if (!res.ok) {
|
|
898
993
|
const e = await res.json().catch(() => ({}));
|
|
899
|
-
throw
|
|
900
|
-
e.detail || e.description || fallbackError
|
|
901
|
-
);
|
|
994
|
+
throw createErrorFromResponse(res.status, e, fallbackError);
|
|
902
995
|
}
|
|
903
996
|
return await res.json();
|
|
904
997
|
}
|
|
@@ -907,9 +1000,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
907
1000
|
const res = await this._auth.request("POST", path, body);
|
|
908
1001
|
if (!res.ok) {
|
|
909
1002
|
const e = await res.json().catch(() => ({}));
|
|
910
|
-
throw
|
|
911
|
-
e.detail || e.description || fallbackError
|
|
912
|
-
);
|
|
1003
|
+
throw createErrorFromResponse(res.status, e, fallbackError);
|
|
913
1004
|
}
|
|
914
1005
|
if (res.status === 204) return {};
|
|
915
1006
|
return await res.json();
|
|
@@ -923,9 +1014,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
923
1014
|
const res = await this._auth.request(method, path, body);
|
|
924
1015
|
if (!res.ok) {
|
|
925
1016
|
const e = await res.json().catch(() => ({}));
|
|
926
|
-
throw
|
|
927
|
-
e.detail || e.description || "Request failed: " + res.status
|
|
928
|
-
);
|
|
1017
|
+
throw createErrorFromResponse(res.status, e, "Request failed: " + res.status);
|
|
929
1018
|
}
|
|
930
1019
|
const ct = res.headers.get("Content-Type") || "";
|
|
931
1020
|
if (res.status === 204 || !ct) return null;
|