@digitraffic/common 2022.10.25-1 → 2022.10.28-2

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 (332) hide show
  1. package/{aws → dist/aws}/infra/api/integration.d.ts +0 -0
  2. package/dist/aws/infra/api/integration.js +52 -0
  3. package/dist/aws/infra/api/integration.js.map +1 -0
  4. package/{aws → dist/aws}/infra/api/response.d.ts +0 -0
  5. package/dist/aws/infra/api/response.js +61 -0
  6. package/dist/aws/infra/api/response.js.map +1 -0
  7. package/{aws → dist/aws}/infra/api/responses.d.ts +3 -3
  8. package/dist/aws/infra/api/responses.js +82 -0
  9. package/dist/aws/infra/api/responses.js.map +1 -0
  10. package/{aws → dist/aws}/infra/api/static-integration.d.ts +0 -0
  11. package/dist/aws/infra/api/static-integration.js +54 -0
  12. package/dist/aws/infra/api/static-integration.js.map +1 -0
  13. package/{aws → dist/aws}/infra/canaries/canary-alarm.d.ts +0 -0
  14. package/dist/aws/infra/canaries/canary-alarm.js +26 -0
  15. package/dist/aws/infra/canaries/canary-alarm.js.map +1 -0
  16. package/dist/aws/infra/canaries/canary-keys.d.ts +3 -0
  17. package/dist/aws/infra/canaries/canary-keys.js +7 -0
  18. package/dist/aws/infra/canaries/canary-keys.js.map +1 -0
  19. package/{aws → dist/aws}/infra/canaries/canary-parameters.d.ts +0 -0
  20. package/dist/aws/infra/canaries/canary-parameters.js +3 -0
  21. package/dist/aws/infra/canaries/canary-parameters.js.map +1 -0
  22. package/{aws → dist/aws}/infra/canaries/canary-role.d.ts +0 -0
  23. package/dist/aws/infra/canaries/canary-role.js +46 -0
  24. package/dist/aws/infra/canaries/canary-role.js.map +1 -0
  25. package/{aws → dist/aws}/infra/canaries/canary.d.ts +0 -0
  26. package/dist/aws/infra/canaries/canary.js +32 -0
  27. package/dist/aws/infra/canaries/canary.js.map +1 -0
  28. package/{aws → dist/aws}/infra/canaries/database-canary.d.ts +0 -0
  29. package/dist/aws/infra/canaries/database-canary.js +70 -0
  30. package/dist/aws/infra/canaries/database-canary.js.map +1 -0
  31. package/{aws → dist/aws}/infra/canaries/database-checker.d.ts +2 -2
  32. package/dist/aws/infra/canaries/database-checker.js +103 -0
  33. package/dist/aws/infra/canaries/database-checker.js.map +1 -0
  34. package/{aws → dist/aws}/infra/canaries/url-canary.d.ts +0 -3
  35. package/dist/aws/infra/canaries/url-canary.js +47 -0
  36. package/dist/aws/infra/canaries/url-canary.js.map +1 -0
  37. package/{aws → dist/aws}/infra/canaries/url-checker.d.ts +0 -0
  38. package/dist/aws/infra/canaries/url-checker.js +252 -0
  39. package/dist/aws/infra/canaries/url-checker.js.map +1 -0
  40. package/{aws → dist/aws}/infra/documentation.d.ts +0 -0
  41. package/dist/aws/infra/documentation.js +95 -0
  42. package/dist/aws/infra/documentation.js.map +1 -0
  43. package/{aws → dist/aws}/infra/scheduler.d.ts +7 -7
  44. package/dist/aws/infra/scheduler.js +31 -0
  45. package/dist/aws/infra/scheduler.js.map +1 -0
  46. package/{aws → dist/aws}/infra/security-rule.d.ts +0 -0
  47. package/dist/aws/infra/security-rule.js +39 -0
  48. package/dist/aws/infra/security-rule.js.map +1 -0
  49. package/{aws → dist/aws}/infra/sqs-integration.d.ts +0 -0
  50. package/dist/aws/infra/sqs-integration.js +93 -0
  51. package/dist/aws/infra/sqs-integration.js.map +1 -0
  52. package/{aws → dist/aws}/infra/sqs-queue.d.ts +0 -0
  53. package/dist/aws/infra/sqs-queue.js +130 -0
  54. package/dist/aws/infra/sqs-queue.js.map +1 -0
  55. package/{aws → dist/aws}/infra/stack/lambda-configs.d.ts +5 -5
  56. package/dist/aws/infra/stack/lambda-configs.js +105 -0
  57. package/dist/aws/infra/stack/lambda-configs.js.map +1 -0
  58. package/{aws → dist/aws}/infra/stack/monitoredfunction.d.ts +1 -1
  59. package/dist/aws/infra/stack/monitoredfunction.js +143 -0
  60. package/dist/aws/infra/stack/monitoredfunction.js.map +1 -0
  61. package/{aws → dist/aws}/infra/stack/rest_apis.d.ts +0 -0
  62. package/dist/aws/infra/stack/rest_apis.js +185 -0
  63. package/dist/aws/infra/stack/rest_apis.js.map +1 -0
  64. package/{aws → dist/aws}/infra/stack/stack-checking-aspect.d.ts +0 -0
  65. package/dist/aws/infra/stack/stack-checking-aspect.js +174 -0
  66. package/dist/aws/infra/stack/stack-checking-aspect.js.map +1 -0
  67. package/{aws → dist/aws}/infra/stack/stack.d.ts +5 -4
  68. package/dist/aws/infra/stack/stack.js +67 -0
  69. package/dist/aws/infra/stack/stack.js.map +1 -0
  70. package/{aws → dist/aws}/infra/stack/subscription.d.ts +3 -3
  71. package/dist/aws/infra/stack/subscription.js +42 -0
  72. package/dist/aws/infra/stack/subscription.js.map +1 -0
  73. package/{aws → dist/aws}/infra/usage-plans.d.ts +0 -0
  74. package/dist/aws/infra/usage-plans.js +42 -0
  75. package/dist/aws/infra/usage-plans.js.map +1 -0
  76. package/{aws → dist/aws}/runtime/apikey.d.ts +0 -0
  77. package/dist/aws/runtime/apikey.js +13 -0
  78. package/dist/aws/runtime/apikey.js.map +1 -0
  79. package/{aws → dist/aws}/runtime/digitraffic-integration-response.d.ts +0 -0
  80. package/dist/aws/runtime/digitraffic-integration-response.js +26 -0
  81. package/dist/aws/runtime/digitraffic-integration-response.js.map +1 -0
  82. package/{aws → dist/aws}/runtime/environment.d.ts +0 -0
  83. package/dist/aws/runtime/environment.js +12 -0
  84. package/dist/aws/runtime/environment.js.map +1 -0
  85. package/{aws → dist/aws}/runtime/messaging.d.ts +0 -0
  86. package/dist/aws/runtime/messaging.js +31 -0
  87. package/dist/aws/runtime/messaging.js.map +1 -0
  88. package/{aws → dist/aws}/runtime/s3.d.ts +0 -0
  89. package/dist/aws/runtime/s3.js +30 -0
  90. package/dist/aws/runtime/s3.js.map +1 -0
  91. package/{aws → dist/aws}/runtime/secrets/dbsecret.d.ts +0 -0
  92. package/dist/aws/runtime/secrets/dbsecret.js +96 -0
  93. package/dist/aws/runtime/secrets/dbsecret.js.map +1 -0
  94. package/{aws → dist/aws}/runtime/secrets/proxy-holder.d.ts +0 -0
  95. package/dist/aws/runtime/secrets/proxy-holder.js +27 -0
  96. package/dist/aws/runtime/secrets/proxy-holder.js.map +1 -0
  97. package/{aws → dist/aws}/runtime/secrets/rds-holder.d.ts +0 -0
  98. package/dist/aws/runtime/secrets/rds-holder.js +27 -0
  99. package/dist/aws/runtime/secrets/rds-holder.js.map +1 -0
  100. package/{aws → dist/aws}/runtime/secrets/secret-holder.d.ts +0 -0
  101. package/dist/aws/runtime/secrets/secret-holder.js +76 -0
  102. package/dist/aws/runtime/secrets/secret-holder.js.map +1 -0
  103. package/{aws → dist/aws}/runtime/secrets/secret.d.ts +0 -0
  104. package/dist/aws/runtime/secrets/secret.js +43 -0
  105. package/dist/aws/runtime/secrets/secret.js.map +1 -0
  106. package/{aws → dist/aws}/types/errors.d.ts +4 -0
  107. package/dist/aws/types/errors.js +16 -0
  108. package/dist/aws/types/errors.js.map +1 -0
  109. package/{aws → dist/aws}/types/lambda-response.d.ts +4 -3
  110. package/dist/aws/types/lambda-response.js +33 -0
  111. package/dist/aws/types/lambda-response.js.map +1 -0
  112. package/{aws → dist/aws}/types/mediatypes.d.ts +1 -1
  113. package/dist/aws/types/mediatypes.js +16 -0
  114. package/dist/aws/types/mediatypes.js.map +1 -0
  115. package/{aws → dist/aws}/types/model-with-reference.d.ts +0 -0
  116. package/dist/aws/types/model-with-reference.js +3 -0
  117. package/dist/aws/types/model-with-reference.js.map +1 -0
  118. package/{aws → dist/aws}/types/proxytypes.d.ts +0 -0
  119. package/dist/aws/types/proxytypes.js +3 -0
  120. package/dist/aws/types/proxytypes.js.map +1 -0
  121. package/{aws → dist/aws}/types/tags.d.ts +0 -0
  122. package/dist/aws/types/tags.js +7 -0
  123. package/dist/aws/types/tags.js.map +1 -0
  124. package/{database → dist/database}/cached.d.ts +0 -0
  125. package/dist/database/cached.js +32 -0
  126. package/dist/database/cached.js.map +1 -0
  127. package/{database → dist/database}/database.d.ts +0 -0
  128. package/dist/database/database.js +70 -0
  129. package/dist/database/database.js.map +1 -0
  130. package/{database → dist/database}/last-updated.d.ts +0 -0
  131. package/dist/database/last-updated.js +54 -0
  132. package/dist/database/last-updated.js.map +1 -0
  133. package/{database → dist/database}/models.d.ts +0 -0
  134. package/dist/database/models.js +3 -0
  135. package/dist/database/models.js.map +1 -0
  136. package/{marine → dist/marine}/id_utils.d.ts +0 -0
  137. package/dist/marine/id_utils.js +33 -0
  138. package/dist/marine/id_utils.js.map +1 -0
  139. package/{marine → dist/marine}/rtz.d.ts +0 -0
  140. package/dist/marine/rtz.js +3 -0
  141. package/dist/marine/rtz.js.map +1 -0
  142. package/{test → dist/test}/asserter.d.ts +0 -0
  143. package/dist/test/asserter.js +45 -0
  144. package/dist/test/asserter.js.map +1 -0
  145. package/{test → dist/test}/db-testutils.d.ts +0 -0
  146. package/dist/test/db-testutils.js +31 -0
  147. package/dist/test/db-testutils.js.map +1 -0
  148. package/{test → dist/test}/httpserver.d.ts +2 -1
  149. package/dist/test/httpserver.js +74 -0
  150. package/dist/test/httpserver.js.map +1 -0
  151. package/{test → dist/test}/secret.d.ts +0 -0
  152. package/dist/test/secret.js +25 -0
  153. package/dist/test/secret.js.map +1 -0
  154. package/{test → dist/test}/secrets-manager.d.ts +0 -0
  155. package/dist/test/secrets-manager.js +59 -0
  156. package/dist/test/secrets-manager.js.map +1 -0
  157. package/{test → dist/test}/testutils.d.ts +0 -0
  158. package/dist/test/testutils.js +44 -0
  159. package/dist/test/testutils.js.map +1 -0
  160. package/dist/types/either.d.ts +9 -0
  161. package/dist/types/either.js +3 -0
  162. package/dist/types/either.js.map +1 -0
  163. package/{types → dist/types}/input-error.d.ts +0 -0
  164. package/dist/types/input-error.js +7 -0
  165. package/dist/types/input-error.js.map +1 -0
  166. package/{types → dist/types}/language.d.ts +0 -0
  167. package/dist/types/language.js +10 -0
  168. package/dist/types/language.js.map +1 -0
  169. package/{types → dist/types}/traffictype.d.ts +0 -0
  170. package/dist/types/traffictype.js +13 -0
  171. package/dist/types/traffictype.js.map +1 -0
  172. package/{types → dist/types}/validator.d.ts +0 -0
  173. package/dist/types/validator.js +14 -0
  174. package/dist/types/validator.js.map +1 -0
  175. package/{utils → dist/utils}/api-model.d.ts +0 -0
  176. package/dist/utils/api-model.js +129 -0
  177. package/dist/utils/api-model.js.map +1 -0
  178. package/{utils → dist/utils}/base64.d.ts +0 -0
  179. package/dist/utils/base64.js +21 -0
  180. package/dist/utils/base64.js.map +1 -0
  181. package/{utils → dist/utils}/date-utils.d.ts +0 -0
  182. package/dist/utils/date-utils.js +34 -0
  183. package/dist/utils/date-utils.js.map +1 -0
  184. package/{utils → dist/utils}/geojson-types.d.ts +0 -0
  185. package/dist/utils/geojson-types.js +18 -0
  186. package/dist/utils/geojson-types.js.map +1 -0
  187. package/{utils → dist/utils}/geometry.d.ts +0 -0
  188. package/dist/utils/geometry.js +164 -0
  189. package/dist/utils/geometry.js.map +1 -0
  190. package/{utils → dist/utils}/retry.d.ts +0 -0
  191. package/dist/utils/retry.js +50 -0
  192. package/dist/utils/retry.js.map +1 -0
  193. package/{utils → dist/utils}/slack.d.ts +0 -0
  194. package/dist/utils/slack.js +25 -0
  195. package/dist/utils/slack.js.map +1 -0
  196. package/{utils → dist/utils}/utils.d.ts +16 -0
  197. package/dist/utils/utils.js +75 -0
  198. package/dist/utils/utils.js.map +1 -0
  199. package/package.json +12 -10
  200. package/src/@types/geojson-validation/index.d.ts +4 -0
  201. package/src/aws/infra/api/integration.ts +73 -0
  202. package/src/aws/infra/api/response.ts +67 -0
  203. package/src/aws/infra/api/responses.ts +124 -0
  204. package/src/aws/infra/api/static-integration.ts +62 -0
  205. package/src/aws/infra/canaries/canary-alarm.ts +31 -0
  206. package/src/aws/infra/canaries/canary-keys.ts +3 -0
  207. package/src/aws/infra/canaries/canary-parameters.ts +19 -0
  208. package/src/aws/infra/canaries/canary-role.ts +47 -0
  209. package/src/aws/infra/canaries/canary.ts +46 -0
  210. package/src/aws/infra/canaries/database-canary.ts +98 -0
  211. package/src/aws/infra/canaries/database-checker.ts +155 -0
  212. package/src/aws/infra/canaries/url-canary.ts +74 -0
  213. package/src/aws/infra/canaries/url-checker.ts +366 -0
  214. package/src/aws/infra/documentation.ts +124 -0
  215. package/src/aws/infra/scheduler.ts +59 -0
  216. package/src/aws/infra/security-rule.ts +38 -0
  217. package/src/aws/infra/sqs-integration.ts +102 -0
  218. package/src/aws/infra/sqs-queue.ts +148 -0
  219. package/src/aws/infra/stack/lambda-configs.ts +207 -0
  220. package/src/aws/infra/stack/monitoredfunction.ts +342 -0
  221. package/src/aws/infra/stack/rest_apis.ts +223 -0
  222. package/src/aws/infra/stack/stack-checking-aspect.ts +279 -0
  223. package/src/aws/infra/stack/stack.ts +145 -0
  224. package/src/aws/infra/stack/subscription.ts +58 -0
  225. package/src/aws/infra/usage-plans.ts +41 -0
  226. package/src/aws/runtime/apikey.ts +9 -0
  227. package/src/aws/runtime/digitraffic-integration-response.ts +28 -0
  228. package/src/aws/runtime/environment.ts +9 -0
  229. package/src/aws/runtime/messaging.ts +26 -0
  230. package/src/aws/runtime/s3.ts +44 -0
  231. package/src/aws/runtime/secrets/dbsecret.ts +116 -0
  232. package/src/aws/runtime/secrets/proxy-holder.ts +37 -0
  233. package/src/aws/runtime/secrets/rds-holder.ts +33 -0
  234. package/src/aws/runtime/secrets/secret-holder.ts +116 -0
  235. package/src/aws/runtime/secrets/secret.ts +50 -0
  236. package/src/aws/types/errors.ts +14 -0
  237. package/src/aws/types/lambda-response.ts +43 -0
  238. package/src/aws/types/mediatypes.ts +11 -0
  239. package/src/aws/types/model-with-reference.ts +8 -0
  240. package/src/aws/types/proxytypes.ts +27 -0
  241. package/src/aws/types/tags.ts +3 -0
  242. package/src/database/cached.ts +35 -0
  243. package/src/database/database.ts +96 -0
  244. package/src/database/last-updated.ts +59 -0
  245. package/src/database/models.ts +7 -0
  246. package/src/marine/id_utils.ts +30 -0
  247. package/src/marine/rtz.ts +57 -0
  248. package/src/test/asserter.ts +48 -0
  249. package/src/test/db-testutils.ts +44 -0
  250. package/src/test/httpserver.ts +96 -0
  251. package/src/test/secret.ts +23 -0
  252. package/src/test/secrets-manager.ts +34 -0
  253. package/src/test/testutils.ts +39 -0
  254. package/src/types/either.ts +3 -0
  255. package/src/types/input-error.ts +2 -0
  256. package/src/types/language.ts +3 -0
  257. package/src/types/traffictype.ts +8 -0
  258. package/src/types/validator.ts +10 -0
  259. package/src/utils/api-model.ts +133 -0
  260. package/src/utils/base64.ts +16 -0
  261. package/src/utils/date-utils.ts +30 -0
  262. package/src/utils/geojson-types.ts +22 -0
  263. package/src/utils/geometry.ts +164 -0
  264. package/src/utils/retry.ts +49 -0
  265. package/src/utils/slack.ts +22 -0
  266. package/src/utils/utils.ts +105 -0
  267. package/aws/infra/api/integration.js +0 -52
  268. package/aws/infra/api/response.js +0 -61
  269. package/aws/infra/api/responses.js +0 -79
  270. package/aws/infra/api/static-integration.js +0 -54
  271. package/aws/infra/canaries/canary-alarm.js +0 -26
  272. package/aws/infra/canaries/canary-parameters.js +0 -3
  273. package/aws/infra/canaries/canary-role.js +0 -46
  274. package/aws/infra/canaries/canary.js +0 -32
  275. package/aws/infra/canaries/database-canary.js +0 -55
  276. package/aws/infra/canaries/database-checker.js +0 -109
  277. package/aws/infra/canaries/url-canary.js +0 -46
  278. package/aws/infra/canaries/url-checker.js +0 -238
  279. package/aws/infra/documentation.js +0 -95
  280. package/aws/infra/scheduler.js +0 -31
  281. package/aws/infra/security-rule.js +0 -39
  282. package/aws/infra/sqs-integration.js +0 -93
  283. package/aws/infra/sqs-queue.js +0 -130
  284. package/aws/infra/stack/lambda-configs.js +0 -93
  285. package/aws/infra/stack/monitoredfunction.js +0 -135
  286. package/aws/infra/stack/rest_apis.js +0 -185
  287. package/aws/infra/stack/stack-checking-aspect.js +0 -174
  288. package/aws/infra/stack/stack.js +0 -60
  289. package/aws/infra/stack/subscription.js +0 -41
  290. package/aws/infra/usage-plans.js +0 -42
  291. package/aws/runtime/apikey.js +0 -13
  292. package/aws/runtime/digitraffic-integration-response.js +0 -26
  293. package/aws/runtime/environment.js +0 -12
  294. package/aws/runtime/messaging.js +0 -31
  295. package/aws/runtime/s3.js +0 -30
  296. package/aws/runtime/secrets/dbsecret.js +0 -96
  297. package/aws/runtime/secrets/proxy-holder.js +0 -26
  298. package/aws/runtime/secrets/rds-holder.js +0 -26
  299. package/aws/runtime/secrets/secret-holder.js +0 -73
  300. package/aws/runtime/secrets/secret.js +0 -43
  301. package/aws/types/errors.js +0 -9
  302. package/aws/types/lambda-response.js +0 -28
  303. package/aws/types/mediatypes.js +0 -15
  304. package/aws/types/model-with-reference.js +0 -3
  305. package/aws/types/proxytypes.js +0 -3
  306. package/aws/types/tags.js +0 -7
  307. package/database/cached.js +0 -32
  308. package/database/database.js +0 -62
  309. package/database/last-updated.js +0 -54
  310. package/database/models.js +0 -3
  311. package/index.d.ts +0 -1
  312. package/index.js +0 -18
  313. package/marine/id_utils.js +0 -33
  314. package/marine/rtz.js +0 -3
  315. package/test/asserter.js +0 -45
  316. package/test/db-testutils.js +0 -31
  317. package/test/httpserver.js +0 -67
  318. package/test/secret.js +0 -25
  319. package/test/secrets-manager.js +0 -59
  320. package/test/testutils.js +0 -44
  321. package/types/input-error.js +0 -7
  322. package/types/language.js +0 -10
  323. package/types/traffictype.js +0 -13
  324. package/types/validator.js +0 -14
  325. package/utils/api-model.js +0 -129
  326. package/utils/base64.js +0 -21
  327. package/utils/date-utils.js +0 -34
  328. package/utils/geojson-types.js +0 -18
  329. package/utils/geometry.js +0 -140
  330. package/utils/retry.js +0 -50
  331. package/utils/slack.js +0 -25
  332. package/utils/utils.js +0 -64
@@ -0,0 +1,116 @@
1
+ import {withSecret, withSecretAndPrefix} from "./secret";
2
+
3
+ export type DbSecret = {
4
+ readonly username: string
5
+ readonly password: string
6
+ readonly host: string
7
+ readonly ro_host: string
8
+ };
9
+
10
+ export enum RdsProxySecretKey {
11
+ username = "username", password = "password", proxy_host = "proxy_host", proxy_ro_host = "proxy_ro_host"
12
+ }
13
+
14
+ export enum RdsSecretKey {
15
+ username = "username", password = "password", host = "host", ro_host = "ro_host"
16
+ }
17
+
18
+ export type RdsProxySecret = Record<RdsProxySecretKey, string>;
19
+ export type RdsSecret = Record<RdsSecretKey, string>;
20
+
21
+ export enum DatabaseEnvironmentKeys {
22
+ DB_USER = "DB_USER",
23
+ DB_PASS = "DB_PASS",
24
+ DB_URI = "DB_URI",
25
+ DB_RO_URI = "DB_RO_URI",
26
+ DB_APPLICATION = "DB_APPLICATION",
27
+ }
28
+
29
+ function setDbSecret(secret: DbSecret) {
30
+ process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
31
+ process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
32
+ process.env[DatabaseEnvironmentKeys.DB_URI] = secret.host;
33
+ process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.ro_host;
34
+ }
35
+
36
+ // cached at Lambda container level
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ let cachedSecret: any;
39
+
40
+ const missingSecretErrorText = 'Missing or empty secretId';
41
+
42
+ /**
43
+ * You can give the following options for retrieving a secret:
44
+ *
45
+ * expectedKeys: the list of keys the secret must include. If not, an error will be thrown.
46
+ * prefix: a prefix that's included in retrieved secret's keys. Only keys begining with the prefix will be included.
47
+ * The secret that is passed to the given function will not include the prefix in it's keys.
48
+
49
+ */
50
+ export type SecretOptions = {
51
+ readonly expectedKeys?: string[],
52
+ readonly prefix?: string
53
+ }
54
+
55
+ export type SecretToPromiseFunction<Secret, Response = void> = (secret: Secret) => Promise<Response> | void;
56
+ export type SecretFunction<Secret, Response = void> = (secretId: string, fn: SecretToPromiseFunction<Secret, Response>, options?: SecretOptions) => Promise<Response | void>;
57
+ export type EmptySecretFunction<Response = void> = SecretFunction<DbSecret, Response>;
58
+
59
+ /**
60
+ * Run the given function with secret retrieved from Secrets Manager. Also injects database-credentials into environment.
61
+ *
62
+ * @deprecated use SecretHolder & ProxyHolder
63
+ * @see SecretOptions
64
+ *
65
+ * @param {string} secretId
66
+ * @param {function} fn
67
+ * @param {SecretOptions} options
68
+ */
69
+ export async function withDbSecret<Secret, Response>(secretId: string, fn: SecretToPromiseFunction<Secret, Response>, options?: SecretOptions): Promise<Response | void> {
70
+ if (!secretId) {
71
+ console.error(missingSecretErrorText);
72
+ return Promise.reject(missingSecretErrorText);
73
+ }
74
+
75
+ if (!cachedSecret) {
76
+ // if prefix is given, first set db values and then fetch secret
77
+ if (options?.prefix) {
78
+ // first set db values
79
+ await withSecret(secretId, (fetchedSecret: DbSecret) => {
80
+ setDbSecret(fetchedSecret);
81
+ });
82
+
83
+ // then actual secret
84
+ await withSecretAndPrefix(secretId, options.prefix, (fetchedSecret: Secret) => {
85
+ cachedSecret = fetchedSecret;
86
+ });
87
+ } else {
88
+ await withSecret(secretId, (fetchedSecret: DbSecret) => {
89
+ setDbSecret(fetchedSecret);
90
+ cachedSecret = fetchedSecret;
91
+ });
92
+ }
93
+ }
94
+ try {
95
+ if (options?.expectedKeys?.length) {
96
+ checkExpectedSecretKeys(options.expectedKeys, cachedSecret);
97
+ }
98
+ return fn(cachedSecret);
99
+ } catch (error) {
100
+ console.error('method=withDbSecret Caught an error, refreshing secret', error);
101
+ // try to refetch secret in case it has changed
102
+ await withSecret(secretId, (fetchedSecret: DbSecret) => {
103
+ setDbSecret(fetchedSecret);
104
+ cachedSecret = fetchedSecret;
105
+ });
106
+ return fn(cachedSecret);
107
+ }
108
+ }
109
+
110
+ export function checkExpectedSecretKeys<Secret>(keys: string[], secret: Secret) {
111
+ const missingKeys = keys.filter(key => !(key in secret));
112
+ if (missingKeys.length) {
113
+ console.error(`method=checkExpectedSecretKeys secret didn't contain the key(s) ${missingKeys}`);
114
+ throw new Error('Expected keys were not found');
115
+ }
116
+ }
@@ -0,0 +1,37 @@
1
+ import { SecretHolder } from "./secret-holder";
2
+ import {
3
+ DatabaseEnvironmentKeys,
4
+ RdsProxySecretKey,
5
+ RdsProxySecret,
6
+ } from "./dbsecret";
7
+ import { getEnvVariable } from "../../../utils/utils";
8
+
9
+ const RDS_PROXY_SECRET_KEYS = Object.values(RdsProxySecretKey);
10
+
11
+ /**
12
+ * Holds credentials for RDS Proxy access.
13
+ */
14
+ export class ProxyHolder {
15
+ private readonly secretHolder;
16
+
17
+ constructor(secretId: string) {
18
+ this.secretHolder = new SecretHolder<RdsProxySecret>(
19
+ secretId,
20
+ "",
21
+ RDS_PROXY_SECRET_KEYS
22
+ );
23
+ }
24
+
25
+ static create() {
26
+ return new ProxyHolder(getEnvVariable("SECRET_ID"));
27
+ }
28
+
29
+ public async setCredentials() {
30
+ const secret = await this.secretHolder.get();
31
+
32
+ process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
33
+ process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
34
+ process.env[DatabaseEnvironmentKeys.DB_URI] = secret.proxy_host;
35
+ process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.proxy_ro_host;
36
+ }
37
+ }
@@ -0,0 +1,33 @@
1
+ import { SecretHolder } from "./secret-holder";
2
+ import { DatabaseEnvironmentKeys, RdsSecret, RdsSecretKey } from "./dbsecret";
3
+ import { getEnvVariable } from "../../../utils/utils";
4
+
5
+ const RDS_SECRET_KEYS = Object.values(RdsSecretKey);
6
+
7
+ /**
8
+ * Holds credentials for RDS access.
9
+ */
10
+ export class RdsHolder {
11
+ private readonly secretHolder;
12
+
13
+ constructor(secretId: string) {
14
+ this.secretHolder = new SecretHolder<RdsSecret>(
15
+ secretId,
16
+ "",
17
+ RDS_SECRET_KEYS
18
+ );
19
+ }
20
+
21
+ static create() {
22
+ return new RdsHolder(getEnvVariable("SECRET_ID"));
23
+ }
24
+
25
+ public async setCredentials() {
26
+ const secret = await this.secretHolder.get();
27
+
28
+ process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
29
+ process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
30
+ process.env[DatabaseEnvironmentKeys.DB_URI] = secret.host;
31
+ process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.ro_host;
32
+ }
33
+ }
@@ -0,0 +1,116 @@
1
+ import { GenericSecret, getSecret } from "./secret";
2
+ import {
3
+ checkExpectedSecretKeys,
4
+ DatabaseEnvironmentKeys,
5
+ DbSecret,
6
+ } from "./dbsecret";
7
+ import { getEnvVariable } from "../../../utils/utils";
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10
+ const NodeTtl = require("node-ttl");
11
+
12
+ const DEFAULT_PREFIX = "";
13
+ const DEFAULT_SECRET_KEY = "SECRET";
14
+ const DEFAULT_CONFIGURATION = {
15
+ ttl: 5 * 60, // timeout secrets in 5 minutes
16
+ };
17
+
18
+ /**
19
+ * Utility class for getting secrets from Secret Manager.
20
+ * Supports prefix for secrets, checking of expected keys and ttl-configuration.
21
+ *
22
+ * By default, secrets are cached for 5 minutes and then reread from the Secrets Manager(This can be overridden with configuration).
23
+ *
24
+ * Supports setting the database environment paramaters from the secret too.
25
+ */
26
+ export class SecretHolder<Secret> {
27
+ private readonly secretId: string;
28
+ private readonly prefix: string;
29
+ private readonly expectedKeys: string[];
30
+
31
+ private readonly secretCache;
32
+
33
+ constructor(
34
+ secretId: string,
35
+ prefix = "",
36
+ expectedKeys: string[] = [],
37
+ configuration = DEFAULT_CONFIGURATION
38
+ ) {
39
+ this.secretId = secretId;
40
+ this.prefix = prefix;
41
+ this.expectedKeys = expectedKeys;
42
+
43
+ this.secretCache = new NodeTtl(configuration);
44
+ }
45
+
46
+ private async initSecret() {
47
+ const secretValue = await getSecret<Secret>(this.secretId);
48
+
49
+ console.info("refreshing secret " + this.secretId);
50
+
51
+ this.secretCache.push(DEFAULT_SECRET_KEY, secretValue);
52
+ }
53
+
54
+ public static create<S>(
55
+ prefix = DEFAULT_PREFIX,
56
+ expectedKeys: string[] = []
57
+ ) {
58
+ return new SecretHolder<S>(
59
+ getEnvVariable("SECRET_ID"),
60
+ prefix,
61
+ expectedKeys
62
+ );
63
+ }
64
+
65
+ public async get(): Promise<Secret> {
66
+ const secret = await this.getSecret<Secret>();
67
+ const parsedSecret =
68
+ this.prefix === DEFAULT_PREFIX
69
+ ? secret
70
+ : this.parseSecret(
71
+ secret as unknown as GenericSecret,
72
+ `${this.prefix}.`
73
+ );
74
+
75
+ if (this.expectedKeys.length > 0) {
76
+ checkExpectedSecretKeys(this.expectedKeys, parsedSecret);
77
+ }
78
+
79
+ return parsedSecret;
80
+ }
81
+
82
+ private parseSecret(secret: GenericSecret, prefix: string): Secret {
83
+ const parsed: GenericSecret = {};
84
+ const skip = prefix.length;
85
+
86
+ for (const key in secret) {
87
+ if (key.startsWith(prefix)) {
88
+ parsed[key.substring(skip)] = secret[key];
89
+ }
90
+ }
91
+
92
+ return parsed as unknown as Secret;
93
+ }
94
+
95
+ private async getSecret<S>(): Promise<S> {
96
+ const secret = this.secretCache.get(DEFAULT_SECRET_KEY);
97
+
98
+ if (!secret) {
99
+ await this.initSecret();
100
+ }
101
+
102
+ return secret || this.secretCache.get(DEFAULT_SECRET_KEY);
103
+ }
104
+
105
+ /**
106
+ * @deprecated Use ProxyHolder
107
+ */
108
+ public async setDatabaseCredentials() {
109
+ const secret = await this.getSecret<DbSecret>();
110
+
111
+ process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
112
+ process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
113
+ process.env[DatabaseEnvironmentKeys.DB_URI] = secret.host;
114
+ process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.ro_host;
115
+ }
116
+ }
@@ -0,0 +1,50 @@
1
+ import {SecretsManager} from 'aws-sdk';
2
+ import {SecretToPromiseFunction} from "./dbsecret";
3
+
4
+ const smClient = new SecretsManager({
5
+ region: process.env.AWS_REGION,
6
+ });
7
+
8
+ export type GenericSecret = Record<string, string>;
9
+
10
+ /**
11
+ @deprecated use SecretHolder & ProxyHolder
12
+ */
13
+ export async function withSecret<Secret, Response>(secretId: string, fn: SecretToPromiseFunction<Secret, Response>): Promise<Response | void> {
14
+ return fn(await getSecret(secretId));
15
+ }
16
+
17
+ export async function getSecret<Secret>(secretId: string, prefix = ''): Promise<Secret> {
18
+ const secretObj = await smClient.getSecretValue({
19
+ SecretId: secretId,
20
+ }).promise();
21
+
22
+ if (!secretObj.SecretString) {
23
+ throw new Error('No secret found!');
24
+ }
25
+
26
+ const secret = JSON.parse(secretObj.SecretString);
27
+
28
+ if (prefix === '') {
29
+ return secret;
30
+ }
31
+
32
+ return parseSecret(secret, `${prefix}.`);
33
+ }
34
+
35
+ function parseSecret<Secret>(secret: GenericSecret, prefix: string): Secret {
36
+ const parsed: GenericSecret = {};
37
+ const skip = prefix.length;
38
+
39
+ for (const key in secret) {
40
+ if (key.startsWith(prefix)) {
41
+ parsed[key.substring(skip)] = secret[key];
42
+ }
43
+ }
44
+
45
+ return parsed as unknown as Secret;
46
+ }
47
+
48
+ export async function withSecretAndPrefix<Secret, Response>(secretId: string, prefix: string, fn: SecretToPromiseFunction<Secret, Response>): Promise<Response | void> {
49
+ return fn(await getSecret(secretId, prefix));
50
+ }
@@ -0,0 +1,14 @@
1
+ // DEPRECATED, remove these!
2
+ export const NOT_FOUND_MESSAGE = "NOT_FOUND";
3
+ export const ERROR_MESSAGE = "ERROR";
4
+ export const OK_MESSAGE = "OK";
5
+ export const BAD_REQUEST_MESSAGE = "BAD REQUEST";
6
+
7
+ export class ValidationError extends Error {
8
+ statusCode: number;
9
+
10
+ constructor(statusCode: number, body: string) {
11
+ super(body);
12
+ this.statusCode = statusCode;
13
+ }
14
+ }
@@ -0,0 +1,43 @@
1
+ export class LambdaResponse<T> {
2
+ readonly status: number;
3
+ readonly body: T;
4
+ readonly fileName?: string;
5
+
6
+ constructor(status: number, body: T, fileName?: string) {
7
+ this.status = status;
8
+ this.body = body;
9
+ this.fileName = fileName;
10
+ }
11
+
12
+ static ok<T>(body: T, fileName?: string) {
13
+ return this.create(200, body, fileName);
14
+ }
15
+
16
+ static okJson<T>(json: T, fileName?: string) {
17
+ return this.create(200, JSON.stringify(json, null, 2), fileName);
18
+ }
19
+
20
+ static badRequest(body: string) {
21
+ return this.create(400, body);
22
+ }
23
+
24
+ static notFound() {
25
+ return this.create(404, "Not found");
26
+ }
27
+
28
+ static internalError() {
29
+ return this.create(500, "Internal error");
30
+ }
31
+
32
+ static notImplemented() {
33
+ return this.create(501, "Not implemented");
34
+ }
35
+
36
+ static create<T>(
37
+ status: number,
38
+ body: T,
39
+ fileName?: string
40
+ ): Promise<LambdaResponse<T>> {
41
+ return Promise.resolve(new LambdaResponse(status, body, fileName));
42
+ }
43
+ }
@@ -0,0 +1,11 @@
1
+ // charset=UTF-8 is deprecated but mobile applications sometimes needs it
2
+ export enum MediaType {
3
+ APPLICATION_JSON = "application/json;charset=UTF-8",
4
+ APPLICATION_XML = "application/xml",
5
+ APPLICATION_GEOJSON = "application/geo+json;charset=UTF-8",
6
+ IMAGE_SVG = "image/svg+xml",
7
+ IMAGE_JPEG = "image/jpeg",
8
+ TEXT_PLAIN = "text/plain",
9
+ TEXT_HTML = "text/html",
10
+ TEXT_CSV = "text/csv",
11
+ }
@@ -0,0 +1,8 @@
1
+ import {Model} from "aws-cdk-lib/aws-apigateway";
2
+
3
+ /**
4
+ * Model object with a reference to an API Gateway model object.
5
+ */
6
+ export interface ModelWithReference extends Model {
7
+ modelReference: string;
8
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
3
+ *
4
+ * Not fully described, extend if necessary.
5
+ */
6
+ export type ProxyLambdaResponse = {
7
+ readonly statusCode: number
8
+ readonly body: string
9
+ readonly headers?: Record<string, string>
10
+ readonly multiValueHeaders?: Record<string, string[]>
11
+ }
12
+
13
+ /**
14
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
15
+ *
16
+ * Not fully described, extend if necessary.
17
+ */
18
+ export type ProxyLambdaRequest = {
19
+ readonly resource: string
20
+ readonly path: string
21
+ readonly httpMethod: string
22
+ readonly headers: Record<string, string>
23
+ readonly multiValueHeaders: Record<string, string[]>
24
+ readonly queryStringParameters: Record<string, string>
25
+ readonly multiValueQueryStringParameters: Record<string, string[]>
26
+ readonly body?: string
27
+ }
@@ -0,0 +1,3 @@
1
+ // DEPRECATED! This must be replaced with new application specific tags
2
+ export const BETA_TAGS = ['Beta'];
3
+ export const DATA_V1_TAGS = ['Data v1'];
@@ -0,0 +1,35 @@
1
+ import {PreparedStatement} from "pg-promise";
2
+ import {DTDatabase, DTTransaction} from "./database";
3
+
4
+ const SQL_UPDATE_CACHE_VALUE =
5
+ `insert into cached_json(cache_id, content, last_updated)
6
+ values ($1, $2, now())
7
+ on conflict(cache_id) do
8
+ update set content = $2, last_updated = now()`;
9
+
10
+ const SQL_GET_CACHE_VALUE =
11
+ `select content, last_updated from cached_json
12
+ where cache_id = $1`;
13
+
14
+ const PS_UPDATE_CACHE_VALUE = new PreparedStatement({
15
+ name: 'update-cache-value',
16
+ text: SQL_UPDATE_CACHE_VALUE,
17
+ });
18
+
19
+ const PS_GET_CACHE_VALUE = new PreparedStatement({
20
+ name: 'get-cache-value',
21
+ text: SQL_GET_CACHE_VALUE,
22
+ });
23
+
24
+ export enum JSON_CACHE_KEY {
25
+ NAUTICAL_WARNINGS_ACTIVE = 'nautical-warnings-active',
26
+ NAUTICAL_WARNINGS_ARCHIVED = 'nautical-warnings-archived'
27
+ }
28
+
29
+ export function updateCachedJson<T>(db: DTDatabase | DTTransaction, cacheKey: JSON_CACHE_KEY, value: T): Promise<null> {
30
+ return db.none(PS_UPDATE_CACHE_VALUE, [cacheKey, value]);
31
+ }
32
+
33
+ export function getJsonFromCache<T>(db: DTDatabase | DTTransaction, cacheKey: JSON_CACHE_KEY): Promise<T | null> {
34
+ return db.oneOrNone(PS_GET_CACHE_VALUE, [cacheKey]).then(value => value?.content ?? null);
35
+ }
@@ -0,0 +1,96 @@
1
+ import { IDatabase, ITask } from "pg-promise";
2
+ import { DatabaseEnvironmentKeys } from "../aws/runtime/secrets/dbsecret";
3
+ import { getEnvVariable, getEnvVariableSafe } from "../utils/utils";
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
6
+ const pgp = require("pg-promise")();
7
+
8
+ // convert numeric types to number instead of string
9
+ pgp.pg.types.setTypeParser(pgp.pg.types.builtins.INT8, (value: string) => {
10
+ return parseInt(value);
11
+ });
12
+
13
+ pgp.pg.types.setTypeParser(pgp.pg.types.builtins.FLOAT8, (value: string) => {
14
+ return parseFloat(value);
15
+ });
16
+
17
+ pgp.pg.types.setTypeParser(pgp.pg.types.builtins.NUMERIC, (value: string) => {
18
+ return parseFloat(value);
19
+ });
20
+
21
+ export type DTDatabase = IDatabase<unknown>;
22
+
23
+ export type DTTransaction = ITask<unknown>;
24
+
25
+ /**
26
+ * Creates a non-pooling database connection primarily used by Lambdas.
27
+ *
28
+ * Note! Using this method opens a new RDS connection on every invocation. It is advised to
29
+ * use RDS proxy to pool connections transparently.
30
+ * https://docs.amazonaws.cn/en_us/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html
31
+ * @param username Username
32
+ * @param password Password
33
+ * @param applicationName name of application
34
+ * @param url Connection URL
35
+ * @param options pg-promise options
36
+ */
37
+ export function initDbConnection(
38
+ username: string,
39
+ password: string,
40
+ applicationName: string,
41
+ url: string,
42
+ options?: object
43
+ ): DTDatabase {
44
+ const finalUrl = `postgresql://${username}:${password}@${url}?application_name=${applicationName}`;
45
+
46
+ return pgp(finalUrl, options);
47
+ }
48
+
49
+ export function inTransaction<T>(
50
+ fn: (db: DTTransaction) => Promise<T>
51
+ ): Promise<T> {
52
+ return inDatabase((db) => db.tx((t: DTTransaction) => fn(t)));
53
+ }
54
+
55
+ export function inDatabase<T>(fn: (db: DTDatabase) => Promise<T>): Promise<T> {
56
+ return doInDatabase(false, fn);
57
+ }
58
+
59
+ export function inDatabaseReadonly<T>(
60
+ fn: (db: DTDatabase) => Promise<T>
61
+ ): Promise<T> {
62
+ return doInDatabase(true, fn);
63
+ }
64
+
65
+ async function doInDatabase<T>(
66
+ readonly: boolean,
67
+ fn: (db: DTDatabase) => Promise<T>
68
+ ): Promise<T> {
69
+ const db_application = getEnvVariableSafe(
70
+ DatabaseEnvironmentKeys.DB_APPLICATION
71
+ );
72
+ const db_ro_uri = getEnvVariableSafe(DatabaseEnvironmentKeys.DB_RO_URI);
73
+ const db_uri =
74
+ db_ro_uri.result === "ok"
75
+ ? db_ro_uri.value
76
+ : getEnvVariable(DatabaseEnvironmentKeys.DB_URI);
77
+ const db = initDbConnection(
78
+ getEnvVariable(DatabaseEnvironmentKeys.DB_USER),
79
+ getEnvVariable(DatabaseEnvironmentKeys.DB_PASS),
80
+ db_application.result === "ok"
81
+ ? db_application.value
82
+ : "unknown-cdk-application",
83
+ db_uri
84
+ );
85
+ try {
86
+ // deallocate all prepared statements to allow for connection pooling
87
+ // DISCARD instead of DEALLOCATE as it didn't always clean all prepared statements
88
+ await db.none("DISCARD ALL");
89
+ return await fn(db);
90
+ } catch (e) {
91
+ console.error("Error in db:", e);
92
+ throw e;
93
+ } finally {
94
+ db.$pool.end();
95
+ }
96
+ }
@@ -0,0 +1,59 @@
1
+ import {DTDatabase, DTTransaction} from "./database";
2
+
3
+ export enum DataType {
4
+ VS_DATEX2="VS_DATEX2",
5
+ COUNTING_SITES_DATA="COUNTING_SITES_DATA",
6
+ COUNTING_SITES_METADATA="COUNTING_SITES_METADATA",
7
+ COUNTING_SITES_METADATA_CHECK="COUNTING_SITES_METADATA_CHECK",
8
+ MAINTENANCE_TRACKING_DATA_CHECKED="MAINTENANCE_TRACKING_DATA_CHECKED",
9
+ PERMIT_DATA="PERMIT_DATA",
10
+ PERMIT_DATA_CHECK="PERMIT_DATA_CHECK",
11
+ }
12
+
13
+ const UNSET_SUBTYPE = '-';
14
+
15
+ type UpdatedTimestamp = {
16
+ updated: Date
17
+ } | null;
18
+
19
+ export function getLastUpdated(db: DTDatabase, datatype: DataType): Promise<Date | null> {
20
+ return db.oneOrNone("select updated from data_updated where data_type=$(datatype) and subtype=$(subtype)", {
21
+ datatype: datatype, subtype: UNSET_SUBTYPE,
22
+ }, (x: UpdatedTimestamp) => x?.updated || null);
23
+ }
24
+
25
+ export function getLastUpdatedWithSubtype(db: DTDatabase, datatype: DataType, subtype: string): Promise<Date | null> {
26
+ return db.oneOrNone("SELECT updated FROM data_updated WHERE data_type=$(datatype) AND subtype=$(subtype)", {
27
+ datatype: datatype, subtype: subtype,
28
+ }, (x: UpdatedTimestamp) => x?.updated || null);
29
+ }
30
+
31
+ export function updateLastUpdated(db: DTDatabase | DTTransaction, datatype: DataType, updated: Date): Promise<null> {
32
+ return db.none(`insert into data_updated(id, data_type, updated)
33
+ values(nextval('seq_data_updated'), $(datatype), $(updated))
34
+ on conflict (data_type, subtype)
35
+ do update set updated = $(updated)`,
36
+ { updated, datatype });
37
+ }
38
+
39
+ export function updateLastUpdatedWithSubtype(db: DTDatabase | DTTransaction, datatype: DataType, subtype: string, updated: Date): Promise<null> {
40
+ return db.none(`insert into data_updated(id, data_type, subtype, updated)
41
+ values(nextval('seq_data_updated'), $(datatype), $(subtype), $(updated))
42
+ on conflict (data_type, subtype)
43
+ do update set updated = $(updated)`,
44
+ { updated, subtype, datatype });
45
+ }
46
+
47
+ export function getUpdatedTimestamp(db: DTDatabase, datatype: string): Promise<Date | null> {
48
+ return db.oneOrNone("select updated_time as updated from updated_timestamp where updated_name=$(datatype)", {
49
+ datatype: datatype,
50
+ }, (x: UpdatedTimestamp) => x?.updated || null);
51
+ }
52
+
53
+ export function updateUpdatedTimestamp(db: DTDatabase | DTTransaction, datatype: string, date: Date, by = ''): Promise<null> {
54
+ return db.none(`insert into updated_timestamp(updated_name, updated_time, updated_by)
55
+ values($(datatype), $(date), $(by))
56
+ on conflict (updated_name)
57
+ do update set updated_time = $(date), updated_by = $(by)`,
58
+ { date, datatype, by });
59
+ }
@@ -0,0 +1,7 @@
1
+ export interface Countable {
2
+ count: number;
3
+ }
4
+
5
+ export interface Identifiable<T> {
6
+ id: T;
7
+ }