@cepseudo/ngsi-ld 1.0.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.
Files changed (142) hide show
  1. package/README.md +425 -0
  2. package/dist/cache/entity_cache.d.ts +42 -0
  3. package/dist/cache/entity_cache.d.ts.map +1 -0
  4. package/dist/cache/entity_cache.js +93 -0
  5. package/dist/cache/entity_cache.js.map +1 -0
  6. package/dist/components/ngsi_ld_collector.d.ts +25 -0
  7. package/dist/components/ngsi_ld_collector.d.ts.map +1 -0
  8. package/dist/components/ngsi_ld_collector.js +13 -0
  9. package/dist/components/ngsi_ld_collector.js.map +1 -0
  10. package/dist/components/ngsi_ld_harvester.d.ts +25 -0
  11. package/dist/components/ngsi_ld_harvester.d.ts.map +1 -0
  12. package/dist/components/ngsi_ld_harvester.js +13 -0
  13. package/dist/components/ngsi_ld_harvester.js.map +1 -0
  14. package/dist/components/type_guards.d.ts +15 -0
  15. package/dist/components/type_guards.d.ts.map +1 -0
  16. package/dist/components/type_guards.js +25 -0
  17. package/dist/components/type_guards.js.map +1 -0
  18. package/dist/endpoints/attrs.d.ts +7 -0
  19. package/dist/endpoints/attrs.d.ts.map +1 -0
  20. package/dist/endpoints/attrs.js +37 -0
  21. package/dist/endpoints/attrs.js.map +1 -0
  22. package/dist/endpoints/entities.d.ts +9 -0
  23. package/dist/endpoints/entities.d.ts.map +1 -0
  24. package/dist/endpoints/entities.js +149 -0
  25. package/dist/endpoints/entities.js.map +1 -0
  26. package/dist/endpoints/subscriptions.d.ts +8 -0
  27. package/dist/endpoints/subscriptions.d.ts.map +1 -0
  28. package/dist/endpoints/subscriptions.js +109 -0
  29. package/dist/endpoints/subscriptions.js.map +1 -0
  30. package/dist/endpoints/types.d.ts +7 -0
  31. package/dist/endpoints/types.d.ts.map +1 -0
  32. package/dist/endpoints/types.js +45 -0
  33. package/dist/endpoints/types.js.map +1 -0
  34. package/dist/helpers/property.d.ts +33 -0
  35. package/dist/helpers/property.d.ts.map +1 -0
  36. package/dist/helpers/property.js +40 -0
  37. package/dist/helpers/property.js.map +1 -0
  38. package/dist/helpers/urn.d.ts +26 -0
  39. package/dist/helpers/urn.d.ts.map +1 -0
  40. package/dist/helpers/urn.js +30 -0
  41. package/dist/helpers/urn.js.map +1 -0
  42. package/dist/index.d.ts +22 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +19 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/models/agrifood/agri_parcel.d.ts +17 -0
  47. package/dist/models/agrifood/agri_parcel.d.ts.map +1 -0
  48. package/dist/models/agrifood/agri_parcel.js +27 -0
  49. package/dist/models/agrifood/agri_parcel.js.map +1 -0
  50. package/dist/models/agrifood/agri_soil_measurement.d.ts +17 -0
  51. package/dist/models/agrifood/agri_soil_measurement.d.ts.map +1 -0
  52. package/dist/models/agrifood/agri_soil_measurement.js +31 -0
  53. package/dist/models/agrifood/agri_soil_measurement.js.map +1 -0
  54. package/dist/models/agrifood/agri_weather_observed.d.ts +20 -0
  55. package/dist/models/agrifood/agri_weather_observed.d.ts.map +1 -0
  56. package/dist/models/agrifood/agri_weather_observed.js +34 -0
  57. package/dist/models/agrifood/agri_weather_observed.js.map +1 -0
  58. package/dist/models/device/device.d.ts +24 -0
  59. package/dist/models/device/device.d.ts.map +1 -0
  60. package/dist/models/device/device.js +39 -0
  61. package/dist/models/device/device.js.map +1 -0
  62. package/dist/models/device/device_measurement.d.ts +16 -0
  63. package/dist/models/device/device_measurement.d.ts.map +1 -0
  64. package/dist/models/device/device_measurement.js +34 -0
  65. package/dist/models/device/device_measurement.js.map +1 -0
  66. package/dist/models/environment/air_quality_observed.d.ts +22 -0
  67. package/dist/models/environment/air_quality_observed.d.ts.map +1 -0
  68. package/dist/models/environment/air_quality_observed.js +50 -0
  69. package/dist/models/environment/air_quality_observed.js.map +1 -0
  70. package/dist/models/environment/noise_level_observed.d.ts +17 -0
  71. package/dist/models/environment/noise_level_observed.d.ts.map +1 -0
  72. package/dist/models/environment/noise_level_observed.js +31 -0
  73. package/dist/models/environment/noise_level_observed.js.map +1 -0
  74. package/dist/models/environment/water_quality_observed.d.ts +20 -0
  75. package/dist/models/environment/water_quality_observed.d.ts.map +1 -0
  76. package/dist/models/environment/water_quality_observed.js +46 -0
  77. package/dist/models/environment/water_quality_observed.js.map +1 -0
  78. package/dist/models/environment/weather_observed.d.ts +19 -0
  79. package/dist/models/environment/weather_observed.d.ts.map +1 -0
  80. package/dist/models/environment/weather_observed.js +46 -0
  81. package/dist/models/environment/weather_observed.js.map +1 -0
  82. package/dist/models/index.d.ts +25 -0
  83. package/dist/models/index.d.ts.map +1 -0
  84. package/dist/models/index.js +17 -0
  85. package/dist/models/index.js.map +1 -0
  86. package/dist/models/smart_city/parking_spot.d.ts +14 -0
  87. package/dist/models/smart_city/parking_spot.d.ts.map +1 -0
  88. package/dist/models/smart_city/parking_spot.js +27 -0
  89. package/dist/models/smart_city/parking_spot.js.map +1 -0
  90. package/dist/models/smart_city/street_light.d.ts +16 -0
  91. package/dist/models/smart_city/street_light.d.ts.map +1 -0
  92. package/dist/models/smart_city/street_light.js +33 -0
  93. package/dist/models/smart_city/street_light.js.map +1 -0
  94. package/dist/models/smart_city/traffic_flow_observed.d.ts +19 -0
  95. package/dist/models/smart_city/traffic_flow_observed.d.ts.map +1 -0
  96. package/dist/models/smart_city/traffic_flow_observed.js +37 -0
  97. package/dist/models/smart_city/traffic_flow_observed.js.map +1 -0
  98. package/dist/notifications/notification_sender.d.ts +16 -0
  99. package/dist/notifications/notification_sender.d.ts.map +1 -0
  100. package/dist/notifications/notification_sender.js +70 -0
  101. package/dist/notifications/notification_sender.js.map +1 -0
  102. package/dist/notifications/notification_worker.d.ts +19 -0
  103. package/dist/notifications/notification_worker.d.ts.map +1 -0
  104. package/dist/notifications/notification_worker.js +65 -0
  105. package/dist/notifications/notification_worker.js.map +1 -0
  106. package/dist/plugin.d.ts +35 -0
  107. package/dist/plugin.d.ts.map +1 -0
  108. package/dist/plugin.js +113 -0
  109. package/dist/plugin.js.map +1 -0
  110. package/dist/subscriptions/q_parser.d.ts +42 -0
  111. package/dist/subscriptions/q_parser.d.ts.map +1 -0
  112. package/dist/subscriptions/q_parser.js +113 -0
  113. package/dist/subscriptions/q_parser.js.map +1 -0
  114. package/dist/subscriptions/subscription_cache.d.ts +46 -0
  115. package/dist/subscriptions/subscription_cache.d.ts.map +1 -0
  116. package/dist/subscriptions/subscription_cache.js +105 -0
  117. package/dist/subscriptions/subscription_cache.js.map +1 -0
  118. package/dist/subscriptions/subscription_matcher.d.ts +23 -0
  119. package/dist/subscriptions/subscription_matcher.d.ts.map +1 -0
  120. package/dist/subscriptions/subscription_matcher.js +84 -0
  121. package/dist/subscriptions/subscription_matcher.js.map +1 -0
  122. package/dist/subscriptions/subscription_store.d.ts +42 -0
  123. package/dist/subscriptions/subscription_store.d.ts.map +1 -0
  124. package/dist/subscriptions/subscription_store.js +189 -0
  125. package/dist/subscriptions/subscription_store.js.map +1 -0
  126. package/dist/types/context.d.ts +9 -0
  127. package/dist/types/context.d.ts.map +1 -0
  128. package/dist/types/context.js +5 -0
  129. package/dist/types/context.js.map +1 -0
  130. package/dist/types/entity.d.ts +46 -0
  131. package/dist/types/entity.d.ts.map +1 -0
  132. package/dist/types/entity.js +2 -0
  133. package/dist/types/entity.js.map +1 -0
  134. package/dist/types/notification.d.ts +22 -0
  135. package/dist/types/notification.d.ts.map +1 -0
  136. package/dist/types/notification.js +2 -0
  137. package/dist/types/notification.js.map +1 -0
  138. package/dist/types/subscription.d.ts +54 -0
  139. package/dist/types/subscription.d.ts.map +1 -0
  140. package/dist/types/subscription.js +2 -0
  141. package/dist/types/subscription.js.map +1 -0
  142. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,425 @@
1
+ # @cepseudo/ngsi-ld
2
+
3
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen)](https://nodejs.org/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
5
+
6
+ Optional NGSI-LD plugin for the Digital Twin Framework. Implements the [ETSI NGSI-LD](https://www.etsi.org/deliver/etsi_gs/CIM/001_099/009/) API specification directly -- no FIWARE or Orion dependency. Provides entity management, subscription-based notifications, and a Redis-backed entity cache for sub-millisecond last-state queries.
7
+
8
+ This package is designed as a **fully optional plugin**. The framework runs without it. When installed, the engine discovers and loads it dynamically at startup.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pnpm add @cepseudo/ngsi-ld
14
+ ```
15
+
16
+ ### Peer dependencies
17
+
18
+ The following must be installed in your project:
19
+
20
+ ```bash
21
+ pnpm add bullmq ioredis
22
+ ```
23
+
24
+ ### Workspace dependencies
25
+
26
+ This package depends on `@cepseudo/shared`, `@cepseudo/database`, and `@cepseudo/components`, which are resolved automatically in the monorepo workspace.
27
+
28
+ ## How the Optional Plugin Pattern Works
29
+
30
+ The engine never imports `@cepseudo/ngsi-ld` statically. Instead, it uses a dynamic import with a try/catch at startup:
31
+
32
+ ```typescript
33
+ // Inside @cepseudo/engine — dynamic discovery
34
+ async function loadOptionalPackages() {
35
+ try {
36
+ const { registerNgsiLd } = await import('@cepseudo/ngsi-ld')
37
+ await registerNgsiLd({ router, db, redis, components, logger })
38
+ logger.info('NGSI-LD plugin loaded')
39
+ } catch {
40
+ // Package not installed — skip silently, framework works without it
41
+ logger.info('NGSI-LD package not installed, skipping')
42
+ }
43
+ }
44
+ ```
45
+
46
+ The integration point is the **EventBus** from `@cepseudo/shared`. When a Collector or Harvester completes, the engine emits a `component:event` on the event bus. If the NGSI-LD plugin is loaded, it listens for these events, converts data to NGSI-LD entities, updates the entity cache, and evaluates subscriptions. If the plugin is not loaded, the events are simply ignored.
47
+
48
+ ```
49
+ Collector writes data --> EventBus emits "component:event"
50
+ |-- ngsi-ld installed --> SubscriptionMatcher evaluates + enqueues notifications
51
+ |-- ngsi-ld not installed --> event ignored, no side effects
52
+ ```
53
+
54
+ No package in layers 0--2 (except ngsi-ld itself) may import from this package. Event names and payload types are defined in `@cepseudo/shared`, not here.
55
+
56
+ ## Core Concepts
57
+
58
+ ### Entities
59
+
60
+ An NGSI-LD entity is a JSON-LD object representing a real-world thing (a sensor, a parking spot, a weather station). Each entity has a URN-style `id`, a `type`, and a set of typed attributes:
61
+
62
+ - **Property** -- holds a scalar or structured value (e.g. temperature, air quality index)
63
+ - **GeoProperty** -- holds a GeoJSON geometry (e.g. sensor location)
64
+ - **Relationship** -- references another entity by URN (e.g. a sensor belongs to a device)
65
+
66
+ Entity last-state is cached in Redis for fast reads. Historical data remains in PostgreSQL.
67
+
68
+ ### Subscriptions
69
+
70
+ Clients register subscriptions to receive webhook notifications when entities matching certain criteria are created or updated. A subscription defines:
71
+
72
+ - `entities` -- which entity types to watch
73
+ - `watchedAttributes` -- optional list of attributes; notifications fire only when these change
74
+ - `q` -- optional filter expression (e.g. `pm25>30;temperature<10`)
75
+ - `notification.endpoint` -- the webhook URL to POST to
76
+ - `throttling` -- minimum seconds between notifications
77
+
78
+ Subscriptions are persisted in PostgreSQL and cached in Redis for fast matching.
79
+
80
+ ### Notifications
81
+
82
+ When an entity update matches a subscription, a notification job is enqueued in BullMQ. A dedicated worker delivers the notification via HTTP POST with exponential backoff retry (up to 3 attempts). Delivery statistics (times sent, times failed, last success) are tracked per subscription.
83
+
84
+ ## API Endpoints
85
+
86
+ All endpoints are mounted under `/ngsi-ld/v1/` and return `application/ld+json`.
87
+
88
+ ### Entities
89
+
90
+ | Method | Path | Description |
91
+ |----------|---------------------------------------------|--------------------------------------|
92
+ | `GET` | `/ngsi-ld/v1/entities` | Query entities by type, q, attrs |
93
+ | `POST` | `/ngsi-ld/v1/entities` | Create or replace an entity |
94
+ | `GET` | `/ngsi-ld/v1/entities/:entityId` | Retrieve a single entity |
95
+ | `PATCH` | `/ngsi-ld/v1/entities/:entityId` | Merge-patch an entity |
96
+ | `DELETE` | `/ngsi-ld/v1/entities/:entityId` | Delete an entity |
97
+ | `PATCH` | `/ngsi-ld/v1/entities/:entityId/attrs` | Update specific attributes |
98
+
99
+ **Query parameters** for `GET /entities`:
100
+
101
+ - `type` -- filter by entity type
102
+ - `q` -- NGSI-LD q-filter expression (e.g. `pm25>30;temperature<10`)
103
+ - `attrs` -- comma-separated list of attributes to project
104
+ - `limit` -- max results (default: 20)
105
+ - `offset` -- pagination offset (default: 0)
106
+
107
+ ### Subscriptions
108
+
109
+ | Method | Path | Description |
110
+ |----------|-------------------------------------------------|--------------------------------------|
111
+ | `POST` | `/ngsi-ld/v1/subscriptions` | Create a subscription |
112
+ | `GET` | `/ngsi-ld/v1/subscriptions` | List all subscriptions |
113
+ | `GET` | `/ngsi-ld/v1/subscriptions/:subscriptionId` | Retrieve a single subscription |
114
+ | `PATCH` | `/ngsi-ld/v1/subscriptions/:subscriptionId` | Partially update a subscription |
115
+ | `DELETE` | `/ngsi-ld/v1/subscriptions/:subscriptionId` | Delete a subscription |
116
+
117
+ ### Types
118
+
119
+ | Method | Path | Description |
120
+ |--------|-------------------------|--------------------------------|
121
+ | `GET` | `/ngsi-ld/v1/types` | List all known entity types |
122
+
123
+ ## Entity Format
124
+
125
+ Entities follow the ETSI NGSI-LD specification with JSON-LD context:
126
+
127
+ ```json
128
+ {
129
+ "id": "urn:ngsi-ld:AirQualityObserved:sensor-42",
130
+ "type": "AirQualityObserved",
131
+ "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
132
+ "pm25": {
133
+ "type": "Property",
134
+ "value": 63.2,
135
+ "observedAt": "2026-02-28T14:32:00Z"
136
+ },
137
+ "no2": {
138
+ "type": "Property",
139
+ "value": 28.1,
140
+ "observedAt": "2026-02-28T14:32:00Z"
141
+ },
142
+ "location": {
143
+ "type": "GeoProperty",
144
+ "value": {
145
+ "type": "Point",
146
+ "coordinates": [4.3517, 50.8503]
147
+ }
148
+ },
149
+ "refDevice": {
150
+ "type": "Relationship",
151
+ "object": "urn:ngsi-ld:Device:weather-station-7"
152
+ }
153
+ }
154
+ ```
155
+
156
+ URNs follow the pattern `urn:ngsi-ld:<Type>:<localId>`. Use the `buildUrn` and `parseUrn` helpers to construct and decompose them.
157
+
158
+ ## Redis Structures
159
+
160
+ ### Entity Cache
161
+
162
+ Each entity is stored as a JSON string under a key derived from its URN. Type indexes allow efficient queries by entity type.
163
+
164
+ ```
165
+ STRING "ngsi:entity:urn:ngsi-ld:AirQualityObserved:sensor-42" --> serialized entity JSON
166
+ SET "ngsi:types" --> { "AirQualityObserved", "WeatherObserved", ... }
167
+ SET "ngsi:type:AirQualityObserved" --> { "urn:ngsi-ld:AirQualityObserved:sensor-42", ... }
168
+ ```
169
+
170
+ - Last-state queries are served from Redis (sub-millisecond).
171
+ - Historical queries are served from PostgreSQL.
172
+
173
+ ### Subscription Cache
174
+
175
+ Active subscriptions are cached in Redis for fast matching on every entity write. Warmed up from PostgreSQL at plugin startup.
176
+
177
+ ```
178
+ STRING "ngsi:sub:<uuid>" --> serialized Subscription JSON
179
+ SET "ngsi:subs:type:AirQualityObserved" --> { "<sub-uuid-1>", "<sub-uuid-7>", ... }
180
+ ```
181
+
182
+ The `SubscriptionMatcher` reads from the subscription cache to evaluate conditions without hitting the database on every write.
183
+
184
+ ## Subscription and Notification Flow
185
+
186
+ ```
187
+ 1. Client POSTs to /ngsi-ld/v1/subscriptions
188
+ --> saved in PostgreSQL + cached in Redis
189
+
190
+ 2. Collector/Harvester writes data
191
+ --> engine emits "component:event" on the EventBus
192
+
193
+ 3. Plugin receives the event
194
+ --> loads the latest data record from the database
195
+ --> calls component.toNgsiLdEntity(data, record)
196
+ --> updates the entity cache in Redis
197
+
198
+ 4. SubscriptionMatcher reads subscription cache
199
+ --> evaluates: entity type match, watchedAttributes change, q-filter, throttling
200
+ --> returns list of matching subscription IDs
201
+
202
+ 5. For each match, a notification job is enqueued in BullMQ
203
+ --> queue: "ngsi-ld-notifications"
204
+ --> 3 attempts with exponential backoff (1s, 5s, 25s)
205
+
206
+ 6. Notification worker POSTs the payload to the subscriber endpoint
207
+ --> Content-Type: application/ld+json
208
+ --> updates times_sent / times_failed in PostgreSQL
209
+ --> updates lastNotificationAt in Redis cache
210
+ ```
211
+
212
+ ### Notification Payload
213
+
214
+ ```json
215
+ {
216
+ "id": "urn:ngsi-ld:Notification:<uuid>",
217
+ "type": "Notification",
218
+ "subscriptionId": "<subscription-uuid>",
219
+ "notifiedAt": "2026-03-19T10:15:00.000Z",
220
+ "data": [
221
+ { "id": "urn:ngsi-ld:AirQualityObserved:sensor-42", "type": "AirQualityObserved", "pm25": { "type": "Property", "value": 63.2 } }
222
+ ]
223
+ }
224
+ ```
225
+
226
+ ## Q-Filter Syntax
227
+
228
+ The `q` parameter supports comparison expressions with `;` as AND:
229
+
230
+ | Operator | Example | Meaning |
231
+ |----------|---------------------|-----------------------------|
232
+ | `==` | `status=="active"` | Equals |
233
+ | `!=` | `status!="offline"` | Not equals |
234
+ | `>` | `pm25>30` | Greater than |
235
+ | `>=` | `temperature>=0` | Greater than or equal |
236
+ | `<` | `humidity<40` | Less than |
237
+ | `<=` | `no2<=50` | Less than or equal |
238
+
239
+ Multiple conditions are ANDed with `;`:
240
+
241
+ ```
242
+ pm25>30;temperature<10;status=="active"
243
+ ```
244
+
245
+ The parser resolves attribute values from NGSI-LD Property objects automatically -- `pm25>30` compares against the `value` field of the `pm25` Property.
246
+
247
+ ## NGSI-LD-Aware Components
248
+
249
+ To have a Collector or Harvester produce NGSI-LD entities automatically, extend the provided abstract base classes instead of the standard ones:
250
+
251
+ ### NgsiLdCollector
252
+
253
+ ```typescript
254
+ import { NgsiLdCollector } from '@cepseudo/ngsi-ld'
255
+ import { buildAirQualityObserved } from '@cepseudo/ngsi-ld'
256
+ import type { DataRecord, NgsiLdEntity } from '@cepseudo/ngsi-ld'
257
+
258
+ export class AirQualityCollector extends NgsiLdCollector {
259
+ getConfiguration() {
260
+ return {
261
+ name: 'air-quality',
262
+ schedule: '*/5 * * * *',
263
+ description: 'Collects air quality data from sensors',
264
+ }
265
+ }
266
+
267
+ async collect() {
268
+ const data = await fetch('https://api.example.com/air-quality')
269
+ return data.json()
270
+ }
271
+
272
+ toNgsiLdEntity(data: unknown, _record: DataRecord): NgsiLdEntity {
273
+ const d = data as { sensorId: string; pm25: number; no2: number; timestamp: string }
274
+ return buildAirQualityObserved({
275
+ localId: d.sensorId,
276
+ pm25: d.pm25,
277
+ no2: d.no2,
278
+ dateObserved: d.timestamp,
279
+ })
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### NgsiLdHarvester
285
+
286
+ Same pattern -- extend `NgsiLdHarvester` and implement `toNgsiLdEntity`. The plugin calls it after each successful harvest.
287
+
288
+ ## Helper Functions
289
+
290
+ ### Property builders
291
+
292
+ ```typescript
293
+ import { property, geoProperty, relationship } from '@cepseudo/ngsi-ld'
294
+
295
+ // Property with value and optional metadata
296
+ property(42.5, { observedAt: '2026-03-19T10:00:00Z', unitCode: 'CEL' })
297
+ // => { type: 'Property', value: 42.5, observedAt: '...', unitCode: 'CEL' }
298
+
299
+ // GeoProperty with GeoJSON
300
+ geoProperty({ type: 'Point', coordinates: [4.3517, 50.8503] })
301
+ // => { type: 'GeoProperty', value: { type: 'Point', coordinates: [...] } }
302
+
303
+ // Relationship to another entity
304
+ relationship('urn:ngsi-ld:Device:station-7')
305
+ // => { type: 'Relationship', object: 'urn:ngsi-ld:Device:station-7' }
306
+ ```
307
+
308
+ ### URN helpers
309
+
310
+ ```typescript
311
+ import { buildUrn, parseUrn } from '@cepseudo/ngsi-ld'
312
+
313
+ buildUrn('AirQualityObserved', 'sensor-42')
314
+ // => 'urn:ngsi-ld:AirQualityObserved:sensor-42'
315
+
316
+ parseUrn('urn:ngsi-ld:AirQualityObserved:sensor-42')
317
+ // => { type: 'AirQualityObserved', localId: 'sensor-42' }
318
+ ```
319
+
320
+ ## Smart Data Models
321
+
322
+ The package includes builder functions for common FIWARE Smart Data Model types. Each builder accepts a typed attributes object and returns a fully formed NGSI-LD entity.
323
+
324
+ **Environment:**
325
+ - `buildAirQualityObserved` -- PM2.5, PM10, NO2, O3, CO, SO2, temperature, humidity
326
+ - `buildWeatherObserved` -- temperature, humidity, wind speed/direction, precipitation
327
+ - `buildWaterQualityObserved` -- pH, dissolved oxygen, conductivity, turbidity
328
+ - `buildNoiseLevelObserved` -- LAeq, LAmax, sones
329
+
330
+ **Smart City:**
331
+ - `buildStreetLight` -- power state, brightness, energy consumption
332
+ - `buildParkingSpot` -- occupancy status, vehicle type
333
+ - `buildTrafficFlowObserved` -- vehicle count, average speed, occupancy
334
+
335
+ **Agrifood:**
336
+ - `buildAgriParcel` -- crop type, area, soil type
337
+ - `buildAgriSoilMeasurement` -- moisture, pH, nitrogen, phosphorus, potassium
338
+ - `buildAgriWeatherObserved` -- solar radiation, evapotranspiration
339
+
340
+ **Device:**
341
+ - `buildDevice` -- device metadata, battery, signal strength
342
+ - `buildDeviceMeasurement` -- generic sensor readings
343
+
344
+ ## Architecture
345
+
346
+ `@cepseudo/ngsi-ld` sits at LAYER 2 in the Digital Twin Framework dependency graph:
347
+
348
+ ```
349
+ LAYER 3: engine -- loads ngsi-ld dynamically via import()
350
+ LAYER 2: ngsi-ld -- entities, subscriptions, notifications
351
+ LAYER 2: assets, components -- business logic, file management
352
+ LAYER 1: database, storage, auth -- infrastructure adapters
353
+ LAYER 0: shared -- types, errors, utilities, validation
354
+ ```
355
+
356
+ Internal structure:
357
+
358
+ ```
359
+ src/
360
+ cache/
361
+ entity_cache.ts -- Redis entity last-state cache
362
+ components/
363
+ ngsi_ld_collector.ts -- Abstract NGSI-LD collector base class
364
+ ngsi_ld_harvester.ts -- Abstract NGSI-LD harvester base class
365
+ type_guards.ts -- Runtime type checks for NGSI-LD components
366
+ endpoints/
367
+ entities.ts -- CRUD /ngsi-ld/v1/entities
368
+ attrs.ts -- PATCH /ngsi-ld/v1/entities/:id/attrs
369
+ subscriptions.ts -- CRUD /ngsi-ld/v1/subscriptions
370
+ types.ts -- GET /ngsi-ld/v1/types
371
+ helpers/
372
+ property.ts -- property(), geoProperty(), relationship() builders
373
+ urn.ts -- buildUrn(), parseUrn()
374
+ models/
375
+ environment/ -- Air quality, weather, water quality, noise
376
+ smart_city/ -- Street lights, parking, traffic
377
+ agrifood/ -- Parcels, soil, weather
378
+ device/ -- Devices, measurements
379
+ notifications/
380
+ notification_sender.ts -- Enqueues notification jobs in BullMQ
381
+ notification_worker.ts -- Delivers notifications via HTTP POST
382
+ subscriptions/
383
+ subscription_store.ts -- PostgreSQL persistence for subscriptions
384
+ subscription_cache.ts -- Redis cache for active subscriptions
385
+ subscription_matcher.ts -- Evaluates subscriptions against entity updates
386
+ q_parser.ts -- Parses and evaluates q-filter expressions
387
+ types/
388
+ entity.ts -- NgsiLdEntity, NgsiLdProperty, etc.
389
+ subscription.ts -- Subscription, SubscriptionCreate
390
+ notification.ts -- NotificationPayload, NotificationJobData
391
+ context.ts -- JSON-LD context constants
392
+ plugin.ts -- registerNgsiLd() entry point
393
+ index.ts -- Public API exports
394
+ ```
395
+
396
+ ## Database Schema
397
+
398
+ The plugin creates its own table on first load (via `subscriptionStore.runMigration()`). No manual migration is needed.
399
+
400
+ ```sql
401
+ CREATE TABLE IF NOT EXISTS ngsi_ld_subscriptions (
402
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
403
+ name VARCHAR(255),
404
+ description TEXT,
405
+ entity_types TEXT[],
406
+ watched_attributes TEXT[],
407
+ q VARCHAR(1000),
408
+ notification_endpoint VARCHAR(500) NOT NULL,
409
+ notification_format VARCHAR(50) DEFAULT 'normalized',
410
+ notification_attrs TEXT[],
411
+ throttling INTEGER DEFAULT 0,
412
+ expires_at TIMESTAMP,
413
+ is_active BOOLEAN DEFAULT true,
414
+ last_notification_at TIMESTAMP,
415
+ last_success_at TIMESTAMP,
416
+ times_sent INTEGER DEFAULT 0,
417
+ times_failed INTEGER DEFAULT 0,
418
+ created_at TIMESTAMP DEFAULT NOW(),
419
+ updated_at TIMESTAMP DEFAULT NOW()
420
+ );
421
+ ```
422
+
423
+ ## License
424
+
425
+ MIT
@@ -0,0 +1,42 @@
1
+ import type { Redis } from 'ioredis';
2
+ import type { NgsiLdEntity } from '../types/entity.js';
3
+ /**
4
+ * Redis-backed cache for NGSI-LD entity last-state.
5
+ *
6
+ * - Each entity is stored as a Redis HASH under `ngsi:entity:<id>`
7
+ * - A Redis SET `ngsi:types` indexes all known entity types
8
+ * - A Redis SET `ngsi:type:<type>` indexes all entity IDs for that type
9
+ */
10
+ export declare class EntityCache {
11
+ #private;
12
+ constructor(redis: Redis);
13
+ /**
14
+ * Stores or overwrites an entity in the cache.
15
+ */
16
+ set(entity: NgsiLdEntity): Promise<void>;
17
+ /**
18
+ * Retrieves an entity by its URN, or null if not found.
19
+ */
20
+ get(id: string): Promise<NgsiLdEntity | null>;
21
+ /**
22
+ * Removes an entity from the cache.
23
+ */
24
+ delete(id: string): Promise<void>;
25
+ /**
26
+ * Returns all entities of a given type.
27
+ */
28
+ listByType(type: string): Promise<NgsiLdEntity[]>;
29
+ /**
30
+ * Returns all known entity types.
31
+ */
32
+ listTypes(): Promise<string[]>;
33
+ /**
34
+ * Returns all cached entities, optionally filtered by type.
35
+ */
36
+ list(options?: {
37
+ type?: string;
38
+ limit?: number;
39
+ offset?: number;
40
+ }): Promise<NgsiLdEntity[]>;
41
+ }
42
+ //# sourceMappingURL=entity_cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity_cache.d.ts","sourceRoot":"","sources":["../../src/cache/entity_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAKtD;;;;;;GAMG;AACH,qBAAa,WAAW;;gBAGR,KAAK,EAAE,KAAK;IAIxB;;OAEG;IACG,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9C;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAMnD;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvC;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAYvD;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAoBpG"}
@@ -0,0 +1,93 @@
1
+ const KEY_PREFIX = 'ngsi:entity:';
2
+ const TYPES_KEY = 'ngsi:types';
3
+ /**
4
+ * Redis-backed cache for NGSI-LD entity last-state.
5
+ *
6
+ * - Each entity is stored as a Redis HASH under `ngsi:entity:<id>`
7
+ * - A Redis SET `ngsi:types` indexes all known entity types
8
+ * - A Redis SET `ngsi:type:<type>` indexes all entity IDs for that type
9
+ */
10
+ export class EntityCache {
11
+ #redis;
12
+ constructor(redis) {
13
+ this.#redis = redis;
14
+ }
15
+ /**
16
+ * Stores or overwrites an entity in the cache.
17
+ */
18
+ async set(entity) {
19
+ const key = `${KEY_PREFIX}${entity.id}`;
20
+ const serialized = JSON.stringify(entity);
21
+ await Promise.all([
22
+ this.#redis.set(key, serialized),
23
+ this.#redis.sadd(TYPES_KEY, entity.type),
24
+ this.#redis.sadd(`ngsi:type:${entity.type}`, entity.id),
25
+ ]);
26
+ }
27
+ /**
28
+ * Retrieves an entity by its URN, or null if not found.
29
+ */
30
+ async get(id) {
31
+ const raw = await this.#redis.get(`${KEY_PREFIX}${id}`);
32
+ if (!raw)
33
+ return null;
34
+ return JSON.parse(raw);
35
+ }
36
+ /**
37
+ * Removes an entity from the cache.
38
+ */
39
+ async delete(id) {
40
+ const existing = await this.get(id);
41
+ if (!existing)
42
+ return;
43
+ await Promise.all([
44
+ this.#redis.del(`${KEY_PREFIX}${id}`),
45
+ this.#redis.srem(`ngsi:type:${existing.type}`, id),
46
+ ]);
47
+ // Clean up the type index entry if no more entities of that type
48
+ const remaining = await this.#redis.scard(`ngsi:type:${existing.type}`);
49
+ if (remaining === 0) {
50
+ await this.#redis.srem(TYPES_KEY, existing.type);
51
+ }
52
+ }
53
+ /**
54
+ * Returns all entities of a given type.
55
+ */
56
+ async listByType(type) {
57
+ const ids = await this.#redis.smembers(`ngsi:type:${type}`);
58
+ if (ids.length === 0)
59
+ return [];
60
+ const keys = ids.map(id => `${KEY_PREFIX}${id}`);
61
+ const raws = await this.#redis.mget(...keys);
62
+ return raws
63
+ .filter((raw) => raw !== null)
64
+ .map(raw => JSON.parse(raw));
65
+ }
66
+ /**
67
+ * Returns all known entity types.
68
+ */
69
+ async listTypes() {
70
+ return this.#redis.smembers(TYPES_KEY);
71
+ }
72
+ /**
73
+ * Returns all cached entities, optionally filtered by type.
74
+ */
75
+ async list(options) {
76
+ if (options?.type) {
77
+ const all = await this.listByType(options.type);
78
+ const offset = options.offset ?? 0;
79
+ const limit = options.limit ?? all.length;
80
+ return all.slice(offset, offset + limit);
81
+ }
82
+ const types = await this.listTypes();
83
+ const results = [];
84
+ for (const type of types) {
85
+ const entities = await this.listByType(type);
86
+ results.push(...entities);
87
+ }
88
+ const offset = options?.offset ?? 0;
89
+ const limit = options?.limit ?? results.length;
90
+ return results.slice(offset, offset + limit);
91
+ }
92
+ }
93
+ //# sourceMappingURL=entity_cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity_cache.js","sourceRoot":"","sources":["../../src/cache/entity_cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { Collector } from '@cepseudo/components';
2
+ import type { DataRecord } from '@cepseudo/shared';
3
+ import type { NgsiLdEntity } from '../types/entity.js';
4
+ /**
5
+ * Abstract base class for NGSI-LD-aware Collectors.
6
+ *
7
+ * Extends the standard Collector with the ability to produce NGSI-LD entities
8
+ * from collected data. The engine's NGSI-LD plugin will call `toNgsiLdEntity`
9
+ * after each successful collection run to update the entity cache.
10
+ *
11
+ * @abstract
12
+ */
13
+ export declare abstract class NgsiLdCollector extends Collector {
14
+ /**
15
+ * Converts the latest collected data into an NGSI-LD entity.
16
+ *
17
+ * Called by the NGSI-LD plugin after each successful `collect()` run.
18
+ *
19
+ * @param data - The parsed JSON data from the most recent collection
20
+ * @param record - The raw DataRecord as stored in the database
21
+ * @returns A fully formed NGSI-LD entity
22
+ */
23
+ abstract toNgsiLdEntity(data: unknown, record: DataRecord): NgsiLdEntity;
24
+ }
25
+ //# sourceMappingURL=ngsi_ld_collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngsi_ld_collector.d.ts","sourceRoot":"","sources":["../../src/components/ngsi_ld_collector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD;;;;;;;;GAQG;AACH,8BAAsB,eAAgB,SAAQ,SAAS;IACnD;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,YAAY;CAC3E"}
@@ -0,0 +1,13 @@
1
+ import { Collector } from '@cepseudo/components';
2
+ /**
3
+ * Abstract base class for NGSI-LD-aware Collectors.
4
+ *
5
+ * Extends the standard Collector with the ability to produce NGSI-LD entities
6
+ * from collected data. The engine's NGSI-LD plugin will call `toNgsiLdEntity`
7
+ * after each successful collection run to update the entity cache.
8
+ *
9
+ * @abstract
10
+ */
11
+ export class NgsiLdCollector extends Collector {
12
+ }
13
+ //# sourceMappingURL=ngsi_ld_collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngsi_ld_collector.js","sourceRoot":"","sources":["../../src/components/ngsi_ld_collector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAIhD;;;;;;;;GAQG;AACH,MAAM,OAAgB,eAAgB,SAAQ,SAAS;CAWtD"}
@@ -0,0 +1,25 @@
1
+ import { Harvester } from '@cepseudo/components';
2
+ import type { DataRecord } from '@cepseudo/shared';
3
+ import type { NgsiLdEntity } from '../types/entity.js';
4
+ /**
5
+ * Abstract base class for NGSI-LD-aware Harvesters.
6
+ *
7
+ * Extends the standard Harvester with the ability to produce NGSI-LD entities
8
+ * from harvested data. The engine's NGSI-LD plugin will call `toNgsiLdEntity`
9
+ * after each successful harvest run to update the entity cache.
10
+ *
11
+ * @abstract
12
+ */
13
+ export declare abstract class NgsiLdHarvester extends Harvester {
14
+ /**
15
+ * Converts the latest harvested data into an NGSI-LD entity.
16
+ *
17
+ * Called by the NGSI-LD plugin after each successful `harvest()` run.
18
+ *
19
+ * @param data - The parsed JSON data from the most recent harvest
20
+ * @param record - The raw DataRecord as stored in the database
21
+ * @returns A fully formed NGSI-LD entity
22
+ */
23
+ abstract toNgsiLdEntity(data: unknown, record: DataRecord): NgsiLdEntity;
24
+ }
25
+ //# sourceMappingURL=ngsi_ld_harvester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngsi_ld_harvester.d.ts","sourceRoot":"","sources":["../../src/components/ngsi_ld_harvester.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD;;;;;;;;GAQG;AACH,8BAAsB,eAAgB,SAAQ,SAAS;IACnD;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,YAAY;CAC3E"}
@@ -0,0 +1,13 @@
1
+ import { Harvester } from '@cepseudo/components';
2
+ /**
3
+ * Abstract base class for NGSI-LD-aware Harvesters.
4
+ *
5
+ * Extends the standard Harvester with the ability to produce NGSI-LD entities
6
+ * from harvested data. The engine's NGSI-LD plugin will call `toNgsiLdEntity`
7
+ * after each successful harvest run to update the entity cache.
8
+ *
9
+ * @abstract
10
+ */
11
+ export class NgsiLdHarvester extends Harvester {
12
+ }
13
+ //# sourceMappingURL=ngsi_ld_harvester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngsi_ld_harvester.js","sourceRoot":"","sources":["../../src/components/ngsi_ld_harvester.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAIhD;;;;;;;;GAQG;AACH,MAAM,OAAgB,eAAgB,SAAQ,SAAS;CAWtD"}