@geolonia/geonicdb-sdk 0.15.0 → 0.17.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 +45 -12
- package/geonicdb.cjs +43 -11
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +155 -14
- package/geonicdb.iife.js +43 -11
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +43 -11
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +156 -15
package/README.md
CHANGED
|
@@ -220,10 +220,25 @@ const entities = await db.getEntities({ type: 'GeoJSON' });
|
|
|
220
220
|
|
|
221
221
|
## Entity CRUD
|
|
222
222
|
|
|
223
|
-
> **Automatic `@context` (#1583)**: the SDK sends requests as
|
|
224
|
-
> which
|
|
225
|
-
> `@context`, the SDK injects the NGSI-LD core
|
|
226
|
-
> `@context` you provide is always preserved (never overwritten).
|
|
223
|
+
> **Automatic `@context` (#1583 / #1599 / #2069)**: the SDK sends requests as
|
|
224
|
+
> `application/ld+json`, which per ETSI GS CIM 009 clause 6.3.5 requires an inline JSON-LD
|
|
225
|
+
> `@context` in the body. When your body omits `@context`, the SDK injects the NGSI-LD core
|
|
226
|
+
> context automatically; a `@context` you provide is always preserved (never overwritten).
|
|
227
|
+
>
|
|
228
|
+
> Injection covers the **whole NGSI-LD API (`/ngsi-ld/v1/**`)** — the same surface where the
|
|
229
|
+
> broker enforces clause 6.3.5 (`/ngsi-ld/v1/jsonldContexts` is excluded, since its body *is*
|
|
230
|
+
> the context document). **Batch array bodies (`/entityOperations/*`) get it per element**,
|
|
231
|
+
> because each element is its own JSON-LD document (clause 5.6.7.3) and an element without a
|
|
232
|
+
> `@context` comes back as a per-entity `BadRequestData` inside a **`207 Multi-Status`**.
|
|
233
|
+
> Arrays of ID strings (`entityOperations/delete`, clause 5.6.10.3) are left alone, as are
|
|
234
|
+
> non-NGSI-LD endpoints (auth / admin / rules), whose `.strict()` schemas reject unknown keys.
|
|
235
|
+
>
|
|
236
|
+
> **Do not attach a `Link` header to writes (#1924)**: ETSI GS CIM 009 clause 6.3.5
|
|
237
|
+
> restricts where `@context` may come from based on `Content-Type` and forbids
|
|
238
|
+
> mixing the two. Since the SDK always sends `application/ld+json`, adding a
|
|
239
|
+
> JSON-LD `Link` header to a `POST` / `PATCH` / `PUT` via the `request()` /
|
|
240
|
+
> `requestRaw()` escape hatch returns `400 BadRequestData`. Put the `@context`
|
|
241
|
+
> in the body for writes; the `Link` header remains the channel for reads.
|
|
227
242
|
|
|
228
243
|
```typescript
|
|
229
244
|
// Create
|
|
@@ -257,9 +272,11 @@ await db.deleteEntity('urn:ngsi-ld:Room:001');
|
|
|
257
272
|
## Types / Attributes Discovery
|
|
258
273
|
|
|
259
274
|
```typescript
|
|
260
|
-
|
|
275
|
+
// getTypes()/getAttributes() は EntityTypeList/AttributeList オブジェクトを返す。
|
|
276
|
+
// 型名・属性名の一覧は typeList / attributeList から取得する。
|
|
277
|
+
const { typeList } = await db.getTypes(); // string[]
|
|
261
278
|
const roomType = await db.getType('Room');
|
|
262
|
-
const
|
|
279
|
+
const { attributeList } = await db.getAttributes(); // string[]
|
|
263
280
|
const tempAttr = await db.getAttribute('temperature');
|
|
264
281
|
```
|
|
265
282
|
|
|
@@ -300,6 +317,11 @@ const result = await db.request('GET', '/ngsi-ld/v1/subscriptions');
|
|
|
300
317
|
|
|
301
318
|
## WebSocket Event Streaming
|
|
302
319
|
|
|
320
|
+
> **NGSI-LD changes only (#2284).** A WebSocket connection streams entity changes made through the
|
|
321
|
+
> NGSI-LD API. Writes performed with the NGSIv2 client (`@geolonia/geonicdb-sdk/ngsi-v2`) or any
|
|
322
|
+
> other `/v2/*` caller are **not** delivered — entities are protocol-isolated and the socket is an
|
|
323
|
+
> NGSI-LD reader. Use HTTP subscriptions (`/v2/subscriptions`) for NGSIv2 changes.
|
|
324
|
+
|
|
303
325
|
```typescript
|
|
304
326
|
db.subscribe({ entityTypes: ['Room'] });
|
|
305
327
|
|
|
@@ -322,6 +344,7 @@ await db.connect();
|
|
|
322
344
|
| `entityDeleted` | `EntityEvent` | Entity was deleted |
|
|
323
345
|
| `open` | — | WebSocket connection opened |
|
|
324
346
|
| `connected` | — | WebSocket ready |
|
|
347
|
+
| `subscribed` | — | `subscribe()` filter is now active on the server. Applying a subscription is asynchronous (the server resolves the supplied `@context` to expand `entityTypes` for matching), so events published before this fires are not guaranteed to be delivered |
|
|
325
348
|
| `close` | `{ code, reason }` | WebSocket closed |
|
|
326
349
|
| `disconnected` | — | Unexpected disconnect |
|
|
327
350
|
| `reconnecting` | `{ attempt, delay }` | Auto-reconnect starting |
|
|
@@ -475,7 +498,9 @@ handle.stop();
|
|
|
475
498
|
| `limit` | `number` | Max results |
|
|
476
499
|
| `offset` | `number` | Pagination offset |
|
|
477
500
|
| `q` | `string` | NGSI-LD query expression |
|
|
478
|
-
| `
|
|
501
|
+
| `datasetId` | `string` | Multi-attribute instance filter (comma-separated; default instance is `@none`). A single match is returned as one Attribute element (ETSI GS CIM 009 clause 4.5.5.1) |
|
|
502
|
+
| `options` | `string` | NGSI-LD representation options (comma-separated): `sysAttrs` (include `createdAt`/`modifiedAt`), `keyValues`, `concise`, `normalized`. Under `keyValues`, multi-attributes (instances with a `datasetId`) are returned as a `dataset` map **only when more than one instance matches**, the default instance keyed by `@none`. A single matching instance stays a bare value (ETSI GS CIM 009 clause 4.5.4 / 4.5.5.1) |
|
|
503
|
+
| `local` | `boolean` | **#2278.** Limit execution to local scope (ETSI GS CIM 009 clause 5.5.13). `GET /ngsi-ld/v1/entities` requires at least one of `type` / `attrs` (with a non-system attribute) / `q` (with a non-system attribute) / a geoquery / local scope (clause 5.7.2.4). **To fetch everything without a filter, pass `local: true`** — otherwise the broker returns `400 BadRequestData` (too wide query) |
|
|
479
504
|
|
|
480
505
|
#### `CountEntitiesParams`
|
|
481
506
|
|
|
@@ -483,14 +508,15 @@ handle.stop();
|
|
|
483
508
|
|----------|------|-------------|
|
|
484
509
|
| `type` | `string` | Entity type filter |
|
|
485
510
|
| `q` | `string` | NGSI-LD query expression |
|
|
511
|
+
| `local` | `boolean` | **#2278.** Limit execution to local scope (ETSI GS CIM 009 clause 5.5.13). `GET /ngsi-ld/v1/entities` requires at least one of `type` / `attrs` (with a non-system attribute) / `q` (with a non-system attribute) / a geoquery / local scope (clause 5.7.2.4). **To fetch everything without a filter, pass `local: true`** — otherwise the broker returns `400 BadRequestData` (too wide query) |
|
|
486
512
|
|
|
487
513
|
### Types / Attributes Discovery
|
|
488
514
|
|
|
489
515
|
| Method | Parameters | Returns | Description |
|
|
490
516
|
|--------|-----------|---------|-------------|
|
|
491
|
-
| `getTypes()` | — | `Promise<
|
|
517
|
+
| `getTypes()` | — | `Promise<EntityTypeList>` | List all entity types (`{ id, type, typeList }`) |
|
|
492
518
|
| `getType(typeName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific entity type |
|
|
493
|
-
| `getAttributes()` | — | `Promise<
|
|
519
|
+
| `getAttributes()` | — | `Promise<AttributeList>` | List all attributes (`{ id, type, attributeList }`) |
|
|
494
520
|
| `getAttribute(attrName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific attribute |
|
|
495
521
|
|
|
496
522
|
### Temporal API
|
|
@@ -500,6 +526,8 @@ handle.stop();
|
|
|
500
526
|
| `getTemporalEntities(params?)` | `GetTemporalEntitiesParams` | `Promise<Record<string, unknown>[]>` | Query temporal entities |
|
|
501
527
|
| `getTemporalEntity(entityId)` | `string` | `Promise<Record<string, unknown>>` | Get temporal representation of a single entity |
|
|
502
528
|
|
|
529
|
+
> Temporal reads answer with **`206 Partial Content`** and a `Content-Range` header when `lastN` is present or the default instance cap actually truncated the history (#2343, ETSI GS CIM 009 clause 6.3.10). Untruncated reads are `200`. Empty results and aggregated representations (`aggrMethods`) remain `200` even when `lastN` is specified, because `Content-Range` cannot be derived. Both methods are unaffected — the SDK checks `res.ok`, which accepts any 2xx. Use `requestRaw()` when you need to read the `Content-Range` value itself.
|
|
530
|
+
|
|
503
531
|
#### `GetTemporalEntitiesParams`
|
|
504
532
|
|
|
505
533
|
| Property | Type | Description |
|
|
@@ -509,20 +537,23 @@ handle.stop();
|
|
|
509
537
|
| `idPattern` | `string` | Entity ID pattern (regex) |
|
|
510
538
|
| `attrs` | `string` | Attribute names to retrieve |
|
|
511
539
|
| `q` | `string` | NGSI-LD query expression |
|
|
512
|
-
| `timeproperty` | `string` | Time property (`observedAt`, `createdAt`, `modifiedAt`) |
|
|
540
|
+
| `timeproperty` | `string` | Time property (`observedAt`, `createdAt`, `modifiedAt`, `deletedAt`; default `observedAt`) |
|
|
513
541
|
| `timeAt` | `string` | Start time (ISO 8601) |
|
|
514
542
|
| `endTimeAt` | `string` | End time (ISO 8601) |
|
|
515
543
|
| `timerel` | `string` | Temporal relation (`before`, `after`, `between`) |
|
|
516
544
|
| `limit` | `number` | Max results |
|
|
517
545
|
| `offset` | `number` | Pagination offset |
|
|
518
|
-
| `options` | `string` | NGSI-LD representation options (comma-separated): `
|
|
546
|
+
| `options` | `string` | NGSI-LD temporal representation options (comma-separated): `temporalValues`, `simplified` (alias), `aggregatedValues` (ETSI clause 4.5.19 shape — one member per aggregation method, each period an array of `[value, start, end]`; `aggrMethods` is required when `aggregatedValues` is specified via `options` in this SDK, and the server applies the same requirement to `format=aggregatedValues`), `sysAttrs` (ETSI 6.3.11 — adds `createdAt` / `modifiedAt` / `expiresAt` to each instance of the normalized representation where known; accepted and ignored for the simplified and aggregated representations). `options` is deprecated in favour of `format` (ETSI 6.3.12); when both are present, **`format` takes precedence** |
|
|
547
|
+
| `format` | `'temporalValues' \| 'aggregatedValues' \| 'simplified'` | NGSI-LD temporal representation format (ETSI 6.3.12): `temporalValues` or `aggregatedValues` (`simplified` is accepted as an alias of `temporalValues`). Unknown values are rejected by the server with `400 InvalidRequest`. **Takes precedence over `options`** when both are present |
|
|
548
|
+
| `aggrMethods` | `string` | Aggregation methods (comma-separated): `totalCount`, `distinctCount`, `sum`, `avg`, `min`, `max`, `stddev`, `sumsq`. Required when `aggregatedValues` is requested via `format` or `options` |
|
|
549
|
+
| `aggrPeriodDuration` | `string` | Aggregation period as an ISO 8601 duration (e.g. `PT1H`). Optional (ETSI Table 5.2.21-1 cardinality 0..1) — when omitted, the whole time range of the query is aggregated as a single period (`PT0S`) |
|
|
519
550
|
|
|
520
551
|
### Batch Operations
|
|
521
552
|
|
|
522
553
|
| Method | Parameters | Returns | Description |
|
|
523
554
|
|--------|-----------|---------|-------------|
|
|
524
555
|
| `batchCreate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Create multiple entities |
|
|
525
|
-
| `batchUpsert(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Upsert multiple entities |
|
|
556
|
+
| `batchUpsert(entities)` | `Record<string, unknown>[]` | `Promise<string[] \| Record<string, unknown>>` | Upsert multiple entities. Per ETSI clause 5.6.8.5 the server returns **201** with a `string[]` of the **created** entity IDs when anything was created, **204** (mapped to `{}`) when every entity already existed, or **207** with `{ created, updated, errors }` on partial failure (#1742) |
|
|
526
557
|
| `batchUpdate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Update multiple entities |
|
|
527
558
|
| `batchDelete(entityIds)` | `string[]` | `Promise<Record<string, unknown>>` | Delete multiple entities by ID |
|
|
528
559
|
|
|
@@ -578,6 +609,8 @@ handle.stop();
|
|
|
578
609
|
|----------|------|-------------|
|
|
579
610
|
| `entityTypes` | `string[]` | Entity types to subscribe to |
|
|
580
611
|
| `idPattern` | `string` | Entity ID regex pattern |
|
|
612
|
+
| `scopeQ` | `string` | NGSI-LD scope query expression (#1132) |
|
|
613
|
+
| `@context` | `string \| string[]` | **#2026.** JSON-LD `@context` used to render delivered events — entity type and attribute names are compacted against it (ETSI GS CIM 009 clause 5.5.7) and echoed back in each event's `@context`. URL string(s) only. **#2055.** It also governs `entityTypes` matching — the `@context` is resolved once at `subscribe` time to expand `entityTypes` into the canonical stored form, so custom terms change which entities match. `idPattern` alone matches the stored form verbatim |
|
|
581
614
|
|
|
582
615
|
### Event Handling
|
|
583
616
|
|
package/geonicdb.cjs
CHANGED
|
@@ -82,7 +82,7 @@ var RECONNECT_MAX_ATTEMPTS = 10;
|
|
|
82
82
|
var RECONNECT_BASE_MS = 1e3;
|
|
83
83
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
84
84
|
var SUB_PROTOCOL = "access_token";
|
|
85
|
-
var NGSI_LD_CORE_CONTEXT = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld";
|
|
85
|
+
var NGSI_LD_CORE_CONTEXT = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.9.jsonld";
|
|
86
86
|
var SDK_CACHE_MAX_ENTRIES_DEFAULT = 1e3;
|
|
87
87
|
var SDK_POLL_INTERVAL_MS_DEFAULT = 5e3;
|
|
88
88
|
var DPOP_IDB_NAME = "geonicdb-sdk";
|
|
@@ -1246,13 +1246,12 @@ var _AuthManager = class _AuthManager {
|
|
|
1246
1246
|
}
|
|
1247
1247
|
const url = this._baseUrl + path;
|
|
1248
1248
|
const isDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && token !== null;
|
|
1249
|
-
const
|
|
1249
|
+
const pathname = path.split("?")[0];
|
|
1250
|
+
const isNgsiLdWrite = pathname.startsWith("/ngsi-ld/v1/") && pathname !== "/ngsi-ld/v1/jsonldContexts";
|
|
1251
|
+
const withCoreContext = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !("@context" in value) ? { ...value, "@context": NGSI_LD_CORE_CONTEXT } : value;
|
|
1250
1252
|
let effectiveBody = body;
|
|
1251
|
-
if (
|
|
1252
|
-
effectiveBody =
|
|
1253
|
-
...effectiveBody,
|
|
1254
|
-
"@context": NGSI_LD_CORE_CONTEXT
|
|
1255
|
-
};
|
|
1253
|
+
if (isNgsiLdWrite && effectiveBody !== void 0) {
|
|
1254
|
+
effectiveBody = Array.isArray(effectiveBody) ? effectiveBody.map(withCoreContext) : withCoreContext(effectiveBody);
|
|
1256
1255
|
}
|
|
1257
1256
|
const bodyStr = effectiveBody !== void 0 ? JSON.stringify(effectiveBody) : void 0;
|
|
1258
1257
|
const doRequest = async (reqToken, dpopNonce) => {
|
|
@@ -1398,6 +1397,7 @@ var WebSocketManager = class {
|
|
|
1398
1397
|
if (options.entityTypes) msg.entityTypes = options.entityTypes;
|
|
1399
1398
|
if (options.idPattern) msg.idPattern = options.idPattern;
|
|
1400
1399
|
if (options.scopeQ) msg.scopeQ = options.scopeQ;
|
|
1400
|
+
if (options["@context"]) msg["@context"] = options["@context"];
|
|
1401
1401
|
}
|
|
1402
1402
|
this._subscription = msg;
|
|
1403
1403
|
if (this._pendingSubscription) return;
|
|
@@ -1465,6 +1465,10 @@ var WebSocketManager = class {
|
|
|
1465
1465
|
return;
|
|
1466
1466
|
}
|
|
1467
1467
|
if (msg.type === "pong") return;
|
|
1468
|
+
if (msg.type === "subscribed") {
|
|
1469
|
+
this._emit("subscribed", void 0);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1468
1472
|
if (msg.type === "dpop_verified") {
|
|
1469
1473
|
this._pendingSubscription = false;
|
|
1470
1474
|
if (this._subscription) {
|
|
@@ -1931,7 +1935,9 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1931
1935
|
limit: 0,
|
|
1932
1936
|
type: params?.type,
|
|
1933
1937
|
q: params?.q,
|
|
1934
|
-
scopeQ: params?.scopeQ
|
|
1938
|
+
scopeQ: params?.scopeQ,
|
|
1939
|
+
// #2278: clause 5.7.2.4 の "too wide query" 免除条件 (e) local scope。
|
|
1940
|
+
local: params?.local
|
|
1935
1941
|
});
|
|
1936
1942
|
const res = await this._auth.request("GET", path);
|
|
1937
1943
|
if (!res.ok) {
|
|
@@ -1967,7 +1973,13 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1967
1973
|
return { deleted: true };
|
|
1968
1974
|
}
|
|
1969
1975
|
// --- Types / Attributes Discovery ---
|
|
1970
|
-
/**
|
|
1976
|
+
/**
|
|
1977
|
+
* List all entity types.
|
|
1978
|
+
*
|
|
1979
|
+
* `details` 未指定の `GET /types` は ETSI OpenAPI v1.8.1 準拠の
|
|
1980
|
+
* `EntityTypeList` オブジェクト(`{ id, type: 'EntityTypeList', typeList }`)を返す。
|
|
1981
|
+
* 型名の一覧は `typeList` から取得する。
|
|
1982
|
+
*/
|
|
1971
1983
|
async getTypes() {
|
|
1972
1984
|
return this._jsonGet("/ngsi-ld/v1/types", "Types query failed");
|
|
1973
1985
|
}
|
|
@@ -1978,7 +1990,13 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1978
1990
|
"Type not found"
|
|
1979
1991
|
);
|
|
1980
1992
|
}
|
|
1981
|
-
/**
|
|
1993
|
+
/**
|
|
1994
|
+
* List all attributes.
|
|
1995
|
+
*
|
|
1996
|
+
* `details` 未指定の `GET /attributes` は ETSI OpenAPI v1.8.1 準拠の
|
|
1997
|
+
* `AttributeList` オブジェクト(`{ id, type: 'AttributeList', attributeList }`)を返す。
|
|
1998
|
+
* 属性名の一覧は `attributeList` から取得する。
|
|
1999
|
+
*/
|
|
1982
2000
|
async getAttributes() {
|
|
1983
2001
|
return this._jsonGet("/ngsi-ld/v1/attributes", "Attributes query failed");
|
|
1984
2002
|
}
|
|
@@ -2016,7 +2034,21 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2016
2034
|
"Batch create failed"
|
|
2017
2035
|
);
|
|
2018
2036
|
}
|
|
2019
|
-
/**
|
|
2037
|
+
/**
|
|
2038
|
+
* Upsert multiple entities in a single request.
|
|
2039
|
+
*
|
|
2040
|
+
* #1742: the server follows ETSI GS CIM 009 clause 5.6.8.5 / Table 6.15.3.1-1:
|
|
2041
|
+
* - **201** with a `string[]` of the **created** entity IDs, when at least one entity
|
|
2042
|
+
* was newly created and none failed (merely updated entities are not listed);
|
|
2043
|
+
* - **204** with no body, when every entity already existed and was updated
|
|
2044
|
+
* (`_jsonPost` maps this to `{}`);
|
|
2045
|
+
* - **207** with a `BatchOperationResult` (`{ created, updated, errors }`), on partial
|
|
2046
|
+
* or total failure.
|
|
2047
|
+
*
|
|
2048
|
+
* The return type reflects all three shapes. Narrow with `Array.isArray(result)` to
|
|
2049
|
+
* detect the "something was created" case.
|
|
2050
|
+
* https://cim.etsi.org/NGSI-LD/official/clause-6.html
|
|
2051
|
+
*/
|
|
2020
2052
|
async batchUpsert(entities) {
|
|
2021
2053
|
return this._jsonPost(
|
|
2022
2054
|
"/ngsi-ld/v1/entityOperations/upsert",
|