@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAG/B,OAAO,EAAU,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEzD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAC7E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAA;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAA;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAkB7D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA4B;IAC7D,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAEtE,mBAAmB;IACnB,MAAM,eAAe,GAAG,IAAI,KAAK,CAAC;QAC9B,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,IAAI;KACzB,CAAC,CAAA;IAEF,wEAAwE;IACxE,MAAM,gBAAgB,GAAsB;QACxC,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,QAAQ,EAAE,WAAW,CAAC,QAAQ;KACjC,CAAA;IAED,wBAAwB;IACxB,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,eAAe,CAAC,CAAA;IACpD,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAAA;IACnD,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAAA;IAChE,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAA;IAE1D,8CAA8C;IAC9C,MAAM,iBAAiB,CAAC,YAAY,EAAE,CAAA;IAEtC,2CAA2C;IAC3C,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,CAAA;IACjD,MAAM,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAEvC,MAAM,CAAC,IAAI,CAAC,+BAA+B,OAAO,CAAC,MAAM,uBAAuB,CAAC,CAAA;IAEjF,4BAA4B;IAC5B,MAAM,iBAAiB,GAAG,IAAI,KAAK,CAAsB,uBAAuB,EAAE;QAC9E,UAAU,EAAE,gBAAgB;KAC/B,CAAC,CAAA;IAEF,0BAA0B;IAC1B,uBAAuB,CAAC,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAA;IAClF,sBAAsB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAC3C,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAA;IAC3E,sBAAsB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAE3C,qCAAqC;IACrC,uBAAuB,CAAC,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAA;IAEvF,uDAAuD;IACvD,cAAc,CAAC,EAAE,CAAC,iBAAiB,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;QAC/C,IACI,KAAK,CAAC,IAAI,KAAK,qBAAqB;YACpC,KAAK,CAAC,IAAI,KAAK,qBAAqB,EACtC,CAAC;YACC,OAAM;QACV,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;YAClC,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAA;YAC7C,MAAM,IAAI,GAAI,CAAmD,CAAC,gBAAgB,EAAE,EAAE,CAAA;YACtF,OAAO,IAAI,EAAE,IAAI,KAAK,KAAK,CAAC,aAAa,CAAA;QAC7C,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,SAAS;YAAE,OAAM;QACtB,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC;YAAE,OAAM;QAE1E,IAAI,CAAC;YACD,sCAAsC;YACtC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;YAC5D,IAAI,CAAC,MAAM;gBAAE,OAAM;YAEnB,iBAAiB;YACjB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAChC,IAAI,IAAa,CAAA;YACjB,IAAI,CAAC;gBACD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;YACtC,CAAC;YAAC,MAAM,CAAC;gBACL,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;YAC1B,CAAC;YAED,4BAA4B;YAC5B,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACrD,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAElD,eAAe;YACf,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAE7B,yBAAyB;YACzB,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,SAAS,CAAC,CAAA;YAE1E,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;gBAClD,IAAI,GAAG,EAAE,CAAC;oBACN,MAAM,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAA;gBAC7D,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,uCAAuC,KAAK,CAAC,aAAa,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClI,CAAC;IACL,CAAC,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { NgsiLdEntity } from '../types/entity.js';
2
+ /**
3
+ * Supported comparison operators in the q-filter language.
4
+ */
5
+ export type QOperator = '==' | '!=' | '>' | '>=' | '<' | '<=';
6
+ /**
7
+ * A single comparison expression node.
8
+ */
9
+ export interface QComparison {
10
+ kind: 'comparison';
11
+ attribute: string;
12
+ operator: QOperator;
13
+ value: string | number | boolean;
14
+ }
15
+ /**
16
+ * An AND chain of comparison expressions.
17
+ */
18
+ export interface QAnd {
19
+ kind: 'and';
20
+ terms: QComparison[];
21
+ }
22
+ /**
23
+ * A parsed q-filter expression (either a single comparison or an AND chain).
24
+ */
25
+ export type QExpr = QComparison | QAnd;
26
+ /**
27
+ * Parses an NGSI-LD q-filter string into an AST.
28
+ *
29
+ * Supports: `attr>val`, `attr==val`, `attr!=val`, `attr>=val`, `attr<=val`, `attr<val`
30
+ * AND chaining with `;` (e.g. `pm25>30;temperature<10`)
31
+ * No OR support in v1.
32
+ *
33
+ * @throws {Error} When a term cannot be parsed.
34
+ */
35
+ export declare function parseQ(q: string): QExpr;
36
+ /**
37
+ * Evaluates a parsed q-filter expression against an NGSI-LD entity.
38
+ *
39
+ * @returns true if the entity matches the filter, false otherwise.
40
+ */
41
+ export declare function evaluateQ(expr: QExpr, entity: NgsiLdEntity): boolean;
42
+ //# sourceMappingURL=q_parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"q_parser.d.ts","sourceRoot":"","sources":["../../src/subscriptions/q_parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAA;AAEtE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAA;AAE7D;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,YAAY,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,IAAI;IACjB,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,WAAW,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,IAAI,CAAA;AAqCtC;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAiBvC;AAyDD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAOpE"}
@@ -0,0 +1,113 @@
1
+ const OPERATOR_PATTERN = /^([a-zA-Z][a-zA-Z0-9_.]*)\s*(==|!=|>=|<=|>|<)\s*(.+)$/;
2
+ /**
3
+ * Parses a single comparison term like `pm25>30` or `status=="ok"`.
4
+ */
5
+ function parseTerm(term) {
6
+ const trimmed = term.trim();
7
+ const match = trimmed.match(OPERATOR_PATTERN);
8
+ if (!match) {
9
+ throw new Error(`Invalid q-filter term: "${term}"`);
10
+ }
11
+ const [, attribute, operator, rawValue] = match;
12
+ let value;
13
+ const unquoted = rawValue.replace(/^["']|["']$/g, '');
14
+ if (rawValue === 'true') {
15
+ value = true;
16
+ }
17
+ else if (rawValue === 'false') {
18
+ value = false;
19
+ }
20
+ else if (!isNaN(Number(rawValue)) && rawValue.trim() !== '') {
21
+ value = Number(rawValue);
22
+ }
23
+ else {
24
+ value = unquoted;
25
+ }
26
+ return {
27
+ kind: 'comparison',
28
+ attribute,
29
+ operator: operator,
30
+ value,
31
+ };
32
+ }
33
+ /**
34
+ * Parses an NGSI-LD q-filter string into an AST.
35
+ *
36
+ * Supports: `attr>val`, `attr==val`, `attr!=val`, `attr>=val`, `attr<=val`, `attr<val`
37
+ * AND chaining with `;` (e.g. `pm25>30;temperature<10`)
38
+ * No OR support in v1.
39
+ *
40
+ * @throws {Error} When a term cannot be parsed.
41
+ */
42
+ export function parseQ(q) {
43
+ const terms = q.split(';').map(t => t.trim()).filter(t => t.length > 0);
44
+ if (terms.length === 0) {
45
+ throw new Error('Empty q-filter expression');
46
+ }
47
+ const parsed = terms.map(parseTerm);
48
+ if (parsed.length === 1) {
49
+ return parsed[0];
50
+ }
51
+ return {
52
+ kind: 'and',
53
+ terms: parsed,
54
+ };
55
+ }
56
+ /**
57
+ * Extracts the numeric/string/boolean value from an NGSI-LD property, or the raw attribute value.
58
+ */
59
+ function resolveAttributeValue(entity, attribute) {
60
+ const attrValue = entity[attribute];
61
+ if (attrValue === undefined || attrValue === null) {
62
+ return undefined;
63
+ }
64
+ // Handle NGSI-LD Property objects
65
+ if (typeof attrValue === 'object' &&
66
+ !Array.isArray(attrValue) &&
67
+ 'type' in attrValue &&
68
+ attrValue.type === 'Property') {
69
+ return attrValue.value;
70
+ }
71
+ // Handle plain scalars (id, type, etc.)
72
+ return attrValue;
73
+ }
74
+ /**
75
+ * Evaluates a single comparison against a resolved value.
76
+ */
77
+ function evaluateComparison(expr, entity) {
78
+ const attrValue = resolveAttributeValue(entity, expr.attribute);
79
+ if (attrValue === undefined || attrValue === null) {
80
+ // Missing attribute: only != returns true
81
+ return expr.operator === '!=';
82
+ }
83
+ // Type coercion: compare numbers as numbers, strings as strings
84
+ const left = typeof attrValue === 'number' ? attrValue : String(attrValue);
85
+ const right = typeof expr.value === 'number' ? expr.value : String(expr.value);
86
+ switch (expr.operator) {
87
+ case '==':
88
+ return left === right;
89
+ case '!=':
90
+ return left !== right;
91
+ case '>':
92
+ return typeof left === 'number' && typeof right === 'number' ? left > right : String(left) > String(right);
93
+ case '>=':
94
+ return typeof left === 'number' && typeof right === 'number' ? left >= right : String(left) >= String(right);
95
+ case '<':
96
+ return typeof left === 'number' && typeof right === 'number' ? left < right : String(left) < String(right);
97
+ case '<=':
98
+ return typeof left === 'number' && typeof right === 'number' ? left <= right : String(left) <= String(right);
99
+ }
100
+ }
101
+ /**
102
+ * Evaluates a parsed q-filter expression against an NGSI-LD entity.
103
+ *
104
+ * @returns true if the entity matches the filter, false otherwise.
105
+ */
106
+ export function evaluateQ(expr, entity) {
107
+ if (expr.kind === 'comparison') {
108
+ return evaluateComparison(expr, entity);
109
+ }
110
+ // AND: all terms must pass
111
+ return expr.terms.every(term => evaluateComparison(term, entity));
112
+ }
113
+ //# sourceMappingURL=q_parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"q_parser.js","sourceRoot":"","sources":["../../src/subscriptions/q_parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { Redis } from 'ioredis';
2
+ import type { Subscription } from '../types/subscription.js';
3
+ /**
4
+ * Redis cache for active subscriptions.
5
+ *
6
+ * Warmed up at plugin startup from PostgreSQL.
7
+ * Updated on every CRUD operation.
8
+ */
9
+ export declare class SubscriptionCache {
10
+ #private;
11
+ constructor(redis: Redis);
12
+ /**
13
+ * Loads all subscriptions from a list into the cache (called at startup).
14
+ */
15
+ warmup(subscriptions: Subscription[]): Promise<void>;
16
+ /**
17
+ * Adds or replaces a subscription in the cache.
18
+ */
19
+ add(sub: Subscription): Promise<void>;
20
+ /**
21
+ * Updates an existing subscription in the cache.
22
+ * Removes old type index entries if entityTypes changed.
23
+ */
24
+ update(sub: Subscription): Promise<void>;
25
+ /**
26
+ * Removes a subscription from the cache.
27
+ */
28
+ remove(id: string): Promise<void>;
29
+ /**
30
+ * Returns all subscription IDs watching a given entity type.
31
+ */
32
+ getIdsByType(type: string): Promise<string[]>;
33
+ /**
34
+ * Returns a subscription by id, or null if not cached.
35
+ */
36
+ getById(id: string): Promise<Subscription | null>;
37
+ /**
38
+ * Returns all subscriptions watching a given entity type.
39
+ */
40
+ getByType(type: string): Promise<Subscription[]>;
41
+ /**
42
+ * Updates the lastNotifiedAt timestamp for a subscription (in-cache only).
43
+ */
44
+ updateLastNotified(id: string, at: string): Promise<void>;
45
+ }
46
+ //# sourceMappingURL=subscription_cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription_cache.d.ts","sourceRoot":"","sources":["../../src/subscriptions/subscription_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAU5D;;;;;GAKG;AACH,qBAAa,iBAAiB;;gBAGd,KAAK,EAAE,KAAK;IAIxB;;OAEG;IACG,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1D;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3C;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9C;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAInD;;OAEG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAMvD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IActD;;OAEG;IACG,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlE"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Redis structure:
3
+ * SET "ngsi:subs:type:<type>" → set of subscription UUIDs
4
+ * HASH "ngsi:sub:<id>" → serialized Subscription JSON
5
+ */
6
+ const SUB_KEY = (id) => `ngsi:sub:${id}`;
7
+ const TYPE_KEY = (type) => `ngsi:subs:type:${type}`;
8
+ /**
9
+ * Redis cache for active subscriptions.
10
+ *
11
+ * Warmed up at plugin startup from PostgreSQL.
12
+ * Updated on every CRUD operation.
13
+ */
14
+ export class SubscriptionCache {
15
+ #redis;
16
+ constructor(redis) {
17
+ this.#redis = redis;
18
+ }
19
+ /**
20
+ * Loads all subscriptions from a list into the cache (called at startup).
21
+ */
22
+ async warmup(subscriptions) {
23
+ for (const sub of subscriptions) {
24
+ await this.add(sub);
25
+ }
26
+ }
27
+ /**
28
+ * Adds or replaces a subscription in the cache.
29
+ */
30
+ async add(sub) {
31
+ await this.#redis.set(SUB_KEY(sub.id), JSON.stringify(sub));
32
+ for (const type of sub.entityTypes) {
33
+ await this.#redis.sadd(TYPE_KEY(type), sub.id);
34
+ }
35
+ }
36
+ /**
37
+ * Updates an existing subscription in the cache.
38
+ * Removes old type index entries if entityTypes changed.
39
+ */
40
+ async update(sub) {
41
+ const existing = await this.getById(sub.id);
42
+ if (existing) {
43
+ // Remove stale type index entries
44
+ for (const type of existing.entityTypes) {
45
+ await this.#redis.srem(TYPE_KEY(type), sub.id);
46
+ }
47
+ }
48
+ await this.add(sub);
49
+ }
50
+ /**
51
+ * Removes a subscription from the cache.
52
+ */
53
+ async remove(id) {
54
+ const sub = await this.getById(id);
55
+ if (!sub)
56
+ return;
57
+ await this.#redis.del(SUB_KEY(id));
58
+ for (const type of sub.entityTypes) {
59
+ await this.#redis.srem(TYPE_KEY(type), id);
60
+ }
61
+ }
62
+ /**
63
+ * Returns all subscription IDs watching a given entity type.
64
+ */
65
+ async getIdsByType(type) {
66
+ return this.#redis.smembers(TYPE_KEY(type));
67
+ }
68
+ /**
69
+ * Returns a subscription by id, or null if not cached.
70
+ */
71
+ async getById(id) {
72
+ const raw = await this.#redis.get(SUB_KEY(id));
73
+ if (!raw)
74
+ return null;
75
+ return JSON.parse(raw);
76
+ }
77
+ /**
78
+ * Returns all subscriptions watching a given entity type.
79
+ */
80
+ async getByType(type) {
81
+ const ids = await this.getIdsByType(type);
82
+ const subs = [];
83
+ for (const id of ids) {
84
+ const sub = await this.getById(id);
85
+ if (sub && sub.isActive) {
86
+ subs.push(sub);
87
+ }
88
+ }
89
+ return subs;
90
+ }
91
+ /**
92
+ * Updates the lastNotifiedAt timestamp for a subscription (in-cache only).
93
+ */
94
+ async updateLastNotified(id, at) {
95
+ const sub = await this.getById(id);
96
+ if (!sub)
97
+ return;
98
+ const updated = {
99
+ ...sub,
100
+ lastNotificationAt: at,
101
+ };
102
+ await this.#redis.set(SUB_KEY(id), JSON.stringify(updated));
103
+ }
104
+ }
105
+ //# sourceMappingURL=subscription_cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription_cache.js","sourceRoot":"","sources":["../../src/subscriptions/subscription_cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { SubscriptionCache } from './subscription_cache.js';
2
+ import type { NgsiLdEntity } from '../types/entity.js';
3
+ /**
4
+ * Evaluates active subscriptions against a newly written entity.
5
+ *
6
+ * A subscription matches when ALL of:
7
+ * 1. The entity type is in the subscription's entityTypes list
8
+ * 2. watchedAttributes — if set, at least one watched attribute changed vs oldEntity
9
+ * 3. q — if set, the new entity passes the q-filter expression
10
+ * 4. throttling — the time since last notification is >= throttling seconds
11
+ */
12
+ export declare class SubscriptionMatcher {
13
+ #private;
14
+ constructor(cache: SubscriptionCache);
15
+ /**
16
+ * Returns the list of subscription IDs that match the given entity update.
17
+ *
18
+ * @param entity - The new entity state
19
+ * @param oldEntity - The previous entity state (undefined for new entities)
20
+ */
21
+ match(entity: NgsiLdEntity, oldEntity?: NgsiLdEntity): Promise<string[]>;
22
+ }
23
+ //# sourceMappingURL=subscription_matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription_matcher.d.ts","sourceRoot":"","sources":["../../src/subscriptions/subscription_matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAA;AAGtE;;;;;;;;GAQG;AACH,qBAAa,mBAAmB;;gBAGhB,KAAK,EAAE,iBAAiB;IAIpC;;;;;OAKG;IACG,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAkEjF"}
@@ -0,0 +1,84 @@
1
+ import { parseQ, evaluateQ } from './q_parser.js';
2
+ /**
3
+ * Evaluates active subscriptions against a newly written entity.
4
+ *
5
+ * A subscription matches when ALL of:
6
+ * 1. The entity type is in the subscription's entityTypes list
7
+ * 2. watchedAttributes — if set, at least one watched attribute changed vs oldEntity
8
+ * 3. q — if set, the new entity passes the q-filter expression
9
+ * 4. throttling — the time since last notification is >= throttling seconds
10
+ */
11
+ export class SubscriptionMatcher {
12
+ #cache;
13
+ constructor(cache) {
14
+ this.#cache = cache;
15
+ }
16
+ /**
17
+ * Returns the list of subscription IDs that match the given entity update.
18
+ *
19
+ * @param entity - The new entity state
20
+ * @param oldEntity - The previous entity state (undefined for new entities)
21
+ */
22
+ async match(entity, oldEntity) {
23
+ const subscriptions = await this.#cache.getByType(entity.type);
24
+ if (subscriptions.length === 0)
25
+ return [];
26
+ const matched = [];
27
+ const now = Date.now();
28
+ for (const sub of subscriptions) {
29
+ // Check expiry
30
+ if (sub.expiresAt && new Date(sub.expiresAt).getTime() < now) {
31
+ continue;
32
+ }
33
+ // Check watchedAttributes: at least one must have changed
34
+ if (sub.watchedAttributes && sub.watchedAttributes.length > 0) {
35
+ const changed = sub.watchedAttributes.some(attr => {
36
+ if (!oldEntity)
37
+ return true; // New entity: all attributes are "changed"
38
+ return !this.#attributesEqual(entity[attr], oldEntity[attr]);
39
+ });
40
+ if (!changed)
41
+ continue;
42
+ }
43
+ // Check q-filter
44
+ if (sub.q) {
45
+ try {
46
+ const expr = parseQ(sub.q);
47
+ if (!evaluateQ(expr, entity))
48
+ continue;
49
+ }
50
+ catch {
51
+ // Malformed q — skip subscription silently
52
+ continue;
53
+ }
54
+ }
55
+ // Check throttling
56
+ if (sub.throttling > 0 && sub.lastNotificationAt) {
57
+ const lastMs = new Date(sub.lastNotificationAt).getTime();
58
+ const elapsedSeconds = (now - lastMs) / 1000;
59
+ if (elapsedSeconds < sub.throttling)
60
+ continue;
61
+ }
62
+ matched.push(sub.id);
63
+ }
64
+ return matched;
65
+ }
66
+ /**
67
+ * Compares two NGSI-LD attribute values for equality.
68
+ */
69
+ #attributesEqual(a, b) {
70
+ if (a === undefined && b === undefined)
71
+ return true;
72
+ if (a === undefined || b === undefined)
73
+ return false;
74
+ // Compare Property values
75
+ if (typeof a === 'object' && a !== null && 'type' in a &&
76
+ a.type === 'Property' &&
77
+ typeof b === 'object' && b !== null && 'type' in b &&
78
+ b.type === 'Property') {
79
+ return JSON.stringify(a.value) === JSON.stringify(b.value);
80
+ }
81
+ return JSON.stringify(a) === JSON.stringify(b);
82
+ }
83
+ }
84
+ //# sourceMappingURL=subscription_matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription_matcher.js","sourceRoot":"","sources":["../../src/subscriptions/subscription_matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { DatabaseAdapter } from '@cepseudo/database';
2
+ import type { Subscription, SubscriptionCreate } from '../types/subscription.js';
3
+ /**
4
+ * PostgreSQL/SQLite persistence layer for NGSI-LD subscriptions.
5
+ *
6
+ * Arrays are serialized as JSON strings for SQLite compatibility.
7
+ * The UUID subscription id is stored in the `sub_id` column; the database
8
+ * integer primary key is used internally for updates.
9
+ */
10
+ export declare class SubscriptionStore {
11
+ #private;
12
+ constructor(db: DatabaseAdapter);
13
+ /**
14
+ * Creates the ngsi_ld_subscriptions table if it does not already exist.
15
+ */
16
+ runMigration(): Promise<void>;
17
+ /**
18
+ * Inserts a new subscription and returns it with generated id and timestamps.
19
+ */
20
+ create(input: SubscriptionCreate): Promise<Subscription>;
21
+ /**
22
+ * Returns all active subscriptions.
23
+ */
24
+ findAll(): Promise<Subscription[]>;
25
+ /**
26
+ * Returns a subscription by its UUID, or null if not found.
27
+ */
28
+ findById(subId: string): Promise<Subscription | null>;
29
+ /**
30
+ * Partially updates a subscription.
31
+ */
32
+ update(subId: string, patch: Partial<SubscriptionCreate>): Promise<Subscription | null>;
33
+ /**
34
+ * Soft-deletes a subscription by setting is_active = 0.
35
+ */
36
+ delete(subId: string): Promise<boolean>;
37
+ /**
38
+ * Updates notification statistics after a delivery attempt.
39
+ */
40
+ recordNotification(subId: string, success: boolean, at: string): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=subscription_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription_store.d.ts","sourceRoot":"","sources":["../../src/subscriptions/subscription_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AA8BhF;;;;;;GAMG;AACH,qBAAa,iBAAiB;;gBAGd,EAAE,EAAE,eAAe;IAI/B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IASnC;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAgC9D;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKxC;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAM3D;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAsB7F;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS7C;;OAEG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA+CvF"}