@geolonia/geonicdb-sdk 0.16.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 +39 -8
- package/geonicdb.cjs +28 -8
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +115 -10
- package/geonicdb.iife.js +28 -8
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +28 -8
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +115 -10
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
|
|
@@ -302,6 +317,11 @@ const result = await db.request('GET', '/ngsi-ld/v1/subscriptions');
|
|
|
302
317
|
|
|
303
318
|
## WebSocket Event Streaming
|
|
304
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
|
+
|
|
305
325
|
```typescript
|
|
306
326
|
db.subscribe({ entityTypes: ['Room'] });
|
|
307
327
|
|
|
@@ -324,6 +344,7 @@ await db.connect();
|
|
|
324
344
|
| `entityDeleted` | `EntityEvent` | Entity was deleted |
|
|
325
345
|
| `open` | — | WebSocket connection opened |
|
|
326
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 |
|
|
327
348
|
| `close` | `{ code, reason }` | WebSocket closed |
|
|
328
349
|
| `disconnected` | — | Unexpected disconnect |
|
|
329
350
|
| `reconnecting` | `{ attempt, delay }` | Auto-reconnect starting |
|
|
@@ -477,7 +498,9 @@ handle.stop();
|
|
|
477
498
|
| `limit` | `number` | Max results |
|
|
478
499
|
| `offset` | `number` | Pagination offset |
|
|
479
500
|
| `q` | `string` | NGSI-LD query expression |
|
|
480
|
-
| `
|
|
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) |
|
|
481
504
|
|
|
482
505
|
#### `CountEntitiesParams`
|
|
483
506
|
|
|
@@ -485,6 +508,7 @@ handle.stop();
|
|
|
485
508
|
|----------|------|-------------|
|
|
486
509
|
| `type` | `string` | Entity type filter |
|
|
487
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) |
|
|
488
512
|
|
|
489
513
|
### Types / Attributes Discovery
|
|
490
514
|
|
|
@@ -502,6 +526,8 @@ handle.stop();
|
|
|
502
526
|
| `getTemporalEntities(params?)` | `GetTemporalEntitiesParams` | `Promise<Record<string, unknown>[]>` | Query temporal entities |
|
|
503
527
|
| `getTemporalEntity(entityId)` | `string` | `Promise<Record<string, unknown>>` | Get temporal representation of a single entity |
|
|
504
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
|
+
|
|
505
531
|
#### `GetTemporalEntitiesParams`
|
|
506
532
|
|
|
507
533
|
| Property | Type | Description |
|
|
@@ -511,20 +537,23 @@ handle.stop();
|
|
|
511
537
|
| `idPattern` | `string` | Entity ID pattern (regex) |
|
|
512
538
|
| `attrs` | `string` | Attribute names to retrieve |
|
|
513
539
|
| `q` | `string` | NGSI-LD query expression |
|
|
514
|
-
| `timeproperty` | `string` | Time property (`observedAt`, `createdAt`, `modifiedAt`) |
|
|
540
|
+
| `timeproperty` | `string` | Time property (`observedAt`, `createdAt`, `modifiedAt`, `deletedAt`; default `observedAt`) |
|
|
515
541
|
| `timeAt` | `string` | Start time (ISO 8601) |
|
|
516
542
|
| `endTimeAt` | `string` | End time (ISO 8601) |
|
|
517
543
|
| `timerel` | `string` | Temporal relation (`before`, `after`, `between`) |
|
|
518
544
|
| `limit` | `number` | Max results |
|
|
519
545
|
| `offset` | `number` | Pagination offset |
|
|
520
|
-
| `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`) |
|
|
521
550
|
|
|
522
551
|
### Batch Operations
|
|
523
552
|
|
|
524
553
|
| Method | Parameters | Returns | Description |
|
|
525
554
|
|--------|-----------|---------|-------------|
|
|
526
555
|
| `batchCreate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Create multiple entities |
|
|
527
|
-
| `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) |
|
|
528
557
|
| `batchUpdate(entities)` | `Record<string, unknown>[]` | `Promise<Record<string, unknown>>` | Update multiple entities |
|
|
529
558
|
| `batchDelete(entityIds)` | `string[]` | `Promise<Record<string, unknown>>` | Delete multiple entities by ID |
|
|
530
559
|
|
|
@@ -580,6 +609,8 @@ handle.stop();
|
|
|
580
609
|
|----------|------|-------------|
|
|
581
610
|
| `entityTypes` | `string[]` | Entity types to subscribe to |
|
|
582
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 |
|
|
583
614
|
|
|
584
615
|
### Event Handling
|
|
585
616
|
|
package/geonicdb.cjs
CHANGED
|
@@ -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) {
|
|
@@ -2028,7 +2034,21 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2028
2034
|
"Batch create failed"
|
|
2029
2035
|
);
|
|
2030
2036
|
}
|
|
2031
|
-
/**
|
|
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
|
+
*/
|
|
2032
2052
|
async batchUpsert(entities) {
|
|
2033
2053
|
return this._jsonPost(
|
|
2034
2054
|
"/ngsi-ld/v1/entityOperations/upsert",
|