@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221

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 (337) hide show
  1. package/CHANGELOG.md +1648 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +12 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +16 -12
  6. package/dist/Emailer/Sendgrid.d.ts +15 -15
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +20 -16
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/fake.js +2 -2
  11. package/dist/Emailer/service.d.ts +13 -4
  12. package/dist/Emailer/service.d.ts.map +1 -1
  13. package/dist/Emailer/service.js +4 -3
  14. package/dist/Emailer.d.ts +1 -1
  15. package/dist/MainFiberSet.d.ts +12 -9
  16. package/dist/MainFiberSet.d.ts.map +1 -1
  17. package/dist/MainFiberSet.js +7 -3
  18. package/dist/Model/Repository/Registry.d.ts +21 -0
  19. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  20. package/dist/Model/Repository/Registry.js +18 -0
  21. package/dist/Model/Repository/ext.d.ts +35 -16
  22. package/dist/Model/Repository/ext.d.ts.map +1 -1
  23. package/dist/Model/Repository/ext.js +60 -3
  24. package/dist/Model/Repository/internal/internal.d.ts +9 -6
  25. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  26. package/dist/Model/Repository/internal/internal.js +115 -51
  27. package/dist/Model/Repository/legacy.d.ts +4 -2
  28. package/dist/Model/Repository/legacy.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.d.ts +10 -6
  30. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  31. package/dist/Model/Repository/makeRepo.js +5 -2
  32. package/dist/Model/Repository/service.d.ts +32 -24
  33. package/dist/Model/Repository/service.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.d.ts +47 -18
  35. package/dist/Model/Repository/validation.d.ts.map +1 -1
  36. package/dist/Model/Repository/validation.js +6 -6
  37. package/dist/Model/Repository.d.ts +2 -1
  38. package/dist/Model/Repository.d.ts.map +1 -1
  39. package/dist/Model/Repository.js +2 -1
  40. package/dist/Model/dsl.d.ts +6 -5
  41. package/dist/Model/dsl.d.ts.map +1 -1
  42. package/dist/Model/dsl.js +2 -3
  43. package/dist/Model/filter/filterApi.d.ts +5 -5
  44. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  45. package/dist/Model/filter/types/errors.d.ts +1 -1
  46. package/dist/Model/filter/types/fields.d.ts +1 -1
  47. package/dist/Model/filter/types/path/common.d.ts +1 -1
  48. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  49. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  50. package/dist/Model/filter/types/path/eager.js +1 -1
  51. package/dist/Model/filter/types/path/index.d.ts +1 -1
  52. package/dist/Model/filter/types/utils.d.ts +1 -1
  53. package/dist/Model/filter/types/validator.d.ts +1 -1
  54. package/dist/Model/filter/types.d.ts +1 -1
  55. package/dist/Model/query/dsl.d.ts +142 -17
  56. package/dist/Model/query/dsl.d.ts.map +1 -1
  57. package/dist/Model/query/dsl.js +190 -5
  58. package/dist/Model/query/new-kid-interpreter.d.ts +77 -8
  59. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  60. package/dist/Model/query/new-kid-interpreter.js +127 -6
  61. package/dist/Model/query.d.ts +1 -1
  62. package/dist/Model.d.ts +2 -1
  63. package/dist/Model.d.ts.map +1 -1
  64. package/dist/Model.js +2 -1
  65. package/dist/QueueMaker/SQLQueue.d.ts +7 -8
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +135 -117
  68. package/dist/QueueMaker/errors.d.ts +5 -3
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/errors.js +4 -2
  71. package/dist/QueueMaker/memQueue.d.ts +9 -5
  72. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  73. package/dist/QueueMaker/memQueue.js +81 -65
  74. package/dist/QueueMaker/sbqueue.d.ts +8 -4
  75. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  76. package/dist/QueueMaker/sbqueue.js +57 -55
  77. package/dist/QueueMaker/service.d.ts +4 -2
  78. package/dist/QueueMaker/service.d.ts.map +1 -1
  79. package/dist/QueueMaker/service.js +1 -1
  80. package/dist/RequestContext.d.ts +75 -35
  81. package/dist/RequestContext.d.ts.map +1 -1
  82. package/dist/RequestContext.js +14 -14
  83. package/dist/RequestFiberSet.d.ts +10 -7
  84. package/dist/RequestFiberSet.d.ts.map +1 -1
  85. package/dist/RequestFiberSet.js +8 -3
  86. package/dist/Store/ContextMapContainer.d.ts +22 -3
  87. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  88. package/dist/Store/ContextMapContainer.js +17 -3
  89. package/dist/Store/Cosmos/query.d.ts +7 -2
  90. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  91. package/dist/Store/Cosmos/query.js +115 -35
  92. package/dist/Store/Cosmos.d.ts +2 -2
  93. package/dist/Store/Cosmos.d.ts.map +1 -1
  94. package/dist/Store/Cosmos.js +343 -244
  95. package/dist/Store/Disk.d.ts +3 -3
  96. package/dist/Store/Disk.d.ts.map +1 -1
  97. package/dist/Store/Disk.js +76 -36
  98. package/dist/Store/Memory.d.ts +7 -4
  99. package/dist/Store/Memory.d.ts.map +1 -1
  100. package/dist/Store/Memory.js +251 -58
  101. package/dist/Store/SQL/Pg.d.ts +4 -0
  102. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  103. package/dist/Store/SQL/Pg.js +233 -0
  104. package/dist/Store/SQL/query.d.ts +43 -0
  105. package/dist/Store/SQL/query.d.ts.map +1 -0
  106. package/dist/Store/SQL/query.js +478 -0
  107. package/dist/Store/SQL.d.ts +21 -0
  108. package/dist/Store/SQL.d.ts.map +1 -0
  109. package/dist/Store/SQL.js +450 -0
  110. package/dist/Store/codeFilter.d.ts +2 -2
  111. package/dist/Store/codeFilter.d.ts.map +1 -1
  112. package/dist/Store/codeFilter.js +6 -3
  113. package/dist/Store/index.d.ts +6 -3
  114. package/dist/Store/index.d.ts.map +1 -1
  115. package/dist/Store/index.js +18 -4
  116. package/dist/Store/service.d.ts +26 -8
  117. package/dist/Store/service.d.ts.map +1 -1
  118. package/dist/Store/service.js +25 -6
  119. package/dist/Store/utils.d.ts +3 -2
  120. package/dist/Store/utils.d.ts.map +1 -1
  121. package/dist/Store/utils.js +5 -5
  122. package/dist/Store.d.ts +1 -1
  123. package/dist/adapters/SQL/Model.d.ts +32 -43
  124. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  125. package/dist/adapters/SQL/Model.js +30 -39
  126. package/dist/adapters/SQL.d.ts +1 -1
  127. package/dist/adapters/ServiceBus.d.ts +14 -11
  128. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  129. package/dist/adapters/ServiceBus.js +30 -21
  130. package/dist/adapters/cosmos-client.d.ts +5 -3
  131. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  132. package/dist/adapters/cosmos-client.js +5 -3
  133. package/dist/adapters/index.d.ts +8 -2
  134. package/dist/adapters/index.d.ts.map +1 -1
  135. package/dist/adapters/index.js +8 -2
  136. package/dist/adapters/logger.d.ts +2 -2
  137. package/dist/adapters/logger.d.ts.map +1 -1
  138. package/dist/adapters/memQueue.d.ts +5 -3
  139. package/dist/adapters/memQueue.d.ts.map +1 -1
  140. package/dist/adapters/memQueue.js +6 -5
  141. package/dist/adapters/mongo-client.d.ts +4 -3
  142. package/dist/adapters/mongo-client.d.ts.map +1 -1
  143. package/dist/adapters/mongo-client.js +5 -3
  144. package/dist/adapters/redis-client.d.ts +6 -3
  145. package/dist/adapters/redis-client.d.ts.map +1 -1
  146. package/dist/adapters/redis-client.js +7 -3
  147. package/dist/api/ContextProvider.d.ts +12 -8
  148. package/dist/api/ContextProvider.d.ts.map +1 -1
  149. package/dist/api/ContextProvider.js +9 -7
  150. package/dist/api/codec.d.ts +1 -1
  151. package/dist/api/internal/RequestContextMiddleware.d.ts +3 -3
  152. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  153. package/dist/api/internal/RequestContextMiddleware.js +10 -6
  154. package/dist/api/internal/auth.d.ts +45 -7
  155. package/dist/api/internal/auth.d.ts.map +1 -1
  156. package/dist/api/internal/auth.js +162 -29
  157. package/dist/api/internal/events.d.ts +6 -4
  158. package/dist/api/internal/events.d.ts.map +1 -1
  159. package/dist/api/internal/events.js +16 -9
  160. package/dist/api/internal/health.d.ts +1 -1
  161. package/dist/api/layerUtils.d.ts +10 -6
  162. package/dist/api/layerUtils.d.ts.map +1 -1
  163. package/dist/api/layerUtils.js +7 -6
  164. package/dist/api/middlewares.d.ts +1 -1
  165. package/dist/api/reportError.d.ts +2 -2
  166. package/dist/api/reportError.d.ts.map +1 -1
  167. package/dist/api/reportError.js +3 -2
  168. package/dist/api/routing/middleware/RouterMiddleware.d.ts +5 -4
  169. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware/middleware.d.ts +42 -3
  171. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  172. package/dist/api/routing/middleware/middleware.js +53 -17
  173. package/dist/api/routing/middleware.d.ts +1 -2
  174. package/dist/api/routing/middleware.d.ts.map +1 -1
  175. package/dist/api/routing/middleware.js +1 -2
  176. package/dist/api/routing/schema/jwt.d.ts +1 -1
  177. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  178. package/dist/api/routing/schema/jwt.js +3 -2
  179. package/dist/api/routing/tsort.d.ts +1 -1
  180. package/dist/api/routing/tsort.d.ts.map +1 -1
  181. package/dist/api/routing/utils.d.ts +4 -4
  182. package/dist/api/routing/utils.d.ts.map +1 -1
  183. package/dist/api/routing/utils.js +3 -2
  184. package/dist/api/routing.d.ts +84 -37
  185. package/dist/api/routing.d.ts.map +1 -1
  186. package/dist/api/routing.js +115 -45
  187. package/dist/api/setupRequest.d.ts +10 -6
  188. package/dist/api/setupRequest.d.ts.map +1 -1
  189. package/dist/api/setupRequest.js +15 -7
  190. package/dist/api/util.d.ts +1 -1
  191. package/dist/arbs.d.ts +2 -2
  192. package/dist/arbs.d.ts.map +1 -1
  193. package/dist/arbs.js +5 -3
  194. package/dist/errorReporter.d.ts +7 -5
  195. package/dist/errorReporter.d.ts.map +1 -1
  196. package/dist/errorReporter.js +22 -26
  197. package/dist/errors.d.ts +1 -1
  198. package/dist/fileUtil.d.ts +2 -2
  199. package/dist/fileUtil.d.ts.map +1 -1
  200. package/dist/fileUtil.js +2 -2
  201. package/dist/index.d.ts +1 -1
  202. package/dist/logger/jsonLogger.d.ts +2 -2
  203. package/dist/logger/jsonLogger.d.ts.map +1 -1
  204. package/dist/logger/jsonLogger.js +4 -2
  205. package/dist/logger/logFmtLogger.d.ts +2 -2
  206. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  207. package/dist/logger/logFmtLogger.js +2 -2
  208. package/dist/logger/shared.d.ts +2 -2
  209. package/dist/logger/shared.d.ts.map +1 -1
  210. package/dist/logger/shared.js +3 -3
  211. package/dist/logger.d.ts +1 -1
  212. package/dist/logger.d.ts.map +1 -1
  213. package/dist/otel.d.ts +75 -0
  214. package/dist/otel.d.ts.map +1 -0
  215. package/dist/otel.js +65 -0
  216. package/dist/rateLimit.d.ts +12 -4
  217. package/dist/rateLimit.d.ts.map +1 -1
  218. package/dist/rateLimit.js +7 -12
  219. package/dist/test.d.ts +3 -3
  220. package/dist/test.d.ts.map +1 -1
  221. package/dist/test.js +2 -2
  222. package/dist/vitest.d.ts +1 -1
  223. package/examples/query.ts +46 -38
  224. package/package.json +46 -37
  225. package/src/CUPS.ts +15 -11
  226. package/src/Emailer/Sendgrid.ts +21 -15
  227. package/src/Emailer/fake.ts +1 -1
  228. package/src/Emailer/service.ts +13 -3
  229. package/src/MainFiberSet.ts +9 -6
  230. package/src/Model/Repository/Registry.ts +34 -0
  231. package/src/Model/Repository/ext.ts +103 -11
  232. package/src/Model/Repository/internal/internal.ts +231 -149
  233. package/src/Model/Repository/legacy.ts +3 -1
  234. package/src/Model/Repository/makeRepo.ts +15 -10
  235. package/src/Model/Repository/service.ts +35 -23
  236. package/src/Model/Repository/validation.ts +5 -5
  237. package/src/Model/Repository.ts +1 -0
  238. package/src/Model/dsl.ts +5 -4
  239. package/src/Model/filter/types/path/eager.ts +1 -2
  240. package/src/Model/query/dsl.ts +353 -19
  241. package/src/Model/query/new-kid-interpreter.ts +211 -6
  242. package/src/Model.ts +1 -0
  243. package/src/QueueMaker/SQLQueue.ts +150 -153
  244. package/src/QueueMaker/errors.ts +3 -1
  245. package/src/QueueMaker/memQueue.ts +111 -105
  246. package/src/QueueMaker/sbqueue.ts +76 -88
  247. package/src/QueueMaker/service.ts +3 -1
  248. package/src/RequestContext.ts +15 -16
  249. package/src/RequestFiberSet.ts +8 -2
  250. package/src/Store/ContextMapContainer.ts +45 -2
  251. package/src/Store/Cosmos/query.ts +143 -44
  252. package/src/Store/Cosmos.ts +491 -350
  253. package/src/Store/Disk.ts +106 -66
  254. package/src/Store/Memory.ts +285 -87
  255. package/src/Store/SQL/Pg.ts +364 -0
  256. package/src/Store/SQL/query.ts +540 -0
  257. package/src/Store/SQL.ts +736 -0
  258. package/src/Store/codeFilter.ts +5 -2
  259. package/src/Store/index.ts +20 -3
  260. package/src/Store/service.ts +45 -10
  261. package/src/Store/utils.ts +25 -23
  262. package/src/adapters/SQL/Model.ts +42 -41
  263. package/src/adapters/ServiceBus.ts +131 -121
  264. package/src/adapters/cosmos-client.ts +4 -2
  265. package/src/adapters/index.ts +7 -0
  266. package/src/adapters/memQueue.ts +5 -4
  267. package/src/adapters/mongo-client.ts +4 -2
  268. package/src/adapters/redis-client.ts +6 -2
  269. package/src/api/ContextProvider.ts +17 -13
  270. package/src/api/internal/RequestContextMiddleware.ts +16 -5
  271. package/src/api/internal/auth.ts +248 -44
  272. package/src/api/internal/events.ts +19 -10
  273. package/src/api/layerUtils.ts +12 -8
  274. package/src/api/reportError.ts +2 -1
  275. package/src/api/routing/middleware/RouterMiddleware.ts +5 -4
  276. package/src/api/routing/middleware/middleware.ts +60 -15
  277. package/src/api/routing/middleware.ts +0 -2
  278. package/src/api/routing/schema/jwt.ts +2 -1
  279. package/src/api/routing/utils.ts +2 -1
  280. package/src/api/routing.ts +304 -131
  281. package/src/api/setupRequest.ts +31 -8
  282. package/src/arbs.ts +5 -3
  283. package/src/errorReporter.ts +65 -75
  284. package/src/fileUtil.ts +1 -1
  285. package/src/logger/jsonLogger.ts +3 -1
  286. package/src/logger/logFmtLogger.ts +1 -1
  287. package/src/logger/shared.ts +3 -2
  288. package/src/otel.ts +152 -0
  289. package/src/rateLimit.ts +34 -23
  290. package/src/test.ts +2 -2
  291. package/test/auth.test.ts +101 -0
  292. package/test/contextProvider.test.ts +14 -11
  293. package/test/controller.test.ts +25 -29
  294. package/test/dist/auth.test.d.ts.map +1 -0
  295. package/test/dist/contextProvider.test.d.ts.map +1 -1
  296. package/test/dist/controller.test.d.ts.map +1 -1
  297. package/test/dist/date-query.test.d.ts.map +1 -0
  298. package/test/dist/fixtures.d.ts +30 -12
  299. package/test/dist/fixtures.d.ts.map +1 -1
  300. package/test/dist/fixtures.js +17 -10
  301. package/test/dist/query.test.d.ts.map +1 -1
  302. package/test/dist/rawQuery.test.d.ts.map +1 -1
  303. package/test/dist/repository-ext.test.d.ts.map +1 -0
  304. package/test/dist/requires.test.d.ts.map +1 -1
  305. package/test/dist/router-generator.test.d.ts.map +1 -0
  306. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  307. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  308. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  309. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  310. package/test/dist/sql-store.test.d.ts.map +1 -0
  311. package/test/fixtures.ts +16 -9
  312. package/test/layerUtils.test.ts +1 -1
  313. package/test/query.test.ts +819 -38
  314. package/test/rawQuery.test.ts +312 -20
  315. package/test/repository-ext.test.ts +62 -0
  316. package/test/requires.test.ts +10 -5
  317. package/test/router-generator.test.ts +187 -0
  318. package/test/routing-interruptibility.test.ts +66 -0
  319. package/test/rpc-e2e-invalidation.test.ts +256 -0
  320. package/test/rpc-multi-middleware.test.ts +84 -9
  321. package/test/rpc-stream-fullstack.test.ts +304 -0
  322. package/test/sql-store.test.ts +1592 -0
  323. package/test/validateSample.test.ts +17 -12
  324. package/tsconfig.examples.json +1 -1
  325. package/tsconfig.json +0 -1
  326. package/tsconfig.json.bak +2 -2
  327. package/tsconfig.src.json +35 -35
  328. package/tsconfig.test.json +2 -2
  329. package/dist/Operations.d.ts +0 -55
  330. package/dist/Operations.d.ts.map +0 -1
  331. package/dist/Operations.js +0 -102
  332. package/dist/OperationsRepo.d.ts +0 -41
  333. package/dist/OperationsRepo.d.ts.map +0 -1
  334. package/dist/OperationsRepo.js +0 -14
  335. package/eslint.config.mjs +0 -24
  336. package/src/Operations.ts +0 -235
  337. package/src/OperationsRepo.ts +0 -16
@@ -1,14 +1,24 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
- import { Array, Duration, Effect, Layer, type NonEmptyReadonlyArray, Option, pipe, Redacted, Struct } from "effect-app"
3
+ import * as Array from "effect-app/Array"
4
+ import type { NonEmptyReadonlyArray } from "effect-app/Array"
4
5
  import { toNonEmptyArray } from "effect-app/Array"
6
+ import * as Effect from "effect-app/Effect"
7
+ import * as Layer from "effect-app/Layer"
8
+ import * as Option from "effect-app/Option"
5
9
  import { dropUndefinedT, mutable } from "effect-app/utils"
10
+ import * as Duration from "effect/Duration"
11
+ import { pipe } from "effect/Function"
12
+ import * as Redacted from "effect/Redacted"
13
+ import * as Struct from "effect/Struct"
6
14
  import { CosmosClient, CosmosClientLayer } from "../adapters/cosmos-client.js"
7
15
  import { OptimisticConcurrencyException } from "../errors.js"
8
16
  import { InfraLogger } from "../logger.js"
9
17
  import type { FieldValues } from "../Model/filter/types.js"
10
- import { type RawQuery } from "../Model/query.js"
18
+ import { type ComputedProjectionIrExpression, type RawQuery } from "../Model/query.js"
19
+ import { annotateCosmosResponse, annotateDb } from "../otel.js"
11
20
  import { buildWhereCosmosQuery3, logQuery } from "./Cosmos/query.js"
21
+ import { storeId } from "./Memory.js"
12
22
  import { type FilterArgs, type PersistenceModelType, type StorageConfig, type Store, type StoreConfig, StoreMaker } from "./service.js"
13
23
 
14
24
  const makeMapId =
@@ -25,148 +35,255 @@ class CosmosDbOperationError {
25
35
  constructor(readonly message: string, readonly raw?: unknown) {}
26
36
  } // TODO: Retry operation when running into RU limit.
27
37
 
28
- function makeCosmosStore({ prefix }: StorageConfig) {
29
- return Effect.gen(function*() {
30
- const { db } = yield* CosmosClient
31
- return {
32
- make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
33
- name: string,
34
- idKey: IdKey,
35
- seed?: Effect.Effect<Iterable<Encoded>, E, R>,
36
- config?: StoreConfig<Encoded>
37
- ) =>
38
- Effect.gen(function*() {
39
- const mapId = makeMapId<IdKey, Encoded>(idKey)
40
- const mapReverseId = makeReverseMapId<IdKey, Encoded>(idKey)
41
- type PM = PersistenceModelType<Encoded>
42
- type PMCosmos = PersistenceModelType<Omit<Encoded, IdKey> & { id: string }>
43
- const containerId = `${prefix}${name}`
44
- yield* Effect.promise(() =>
45
- db.containers.createIfNotExists(dropUndefinedT({
46
- id: containerId,
47
- uniqueKeyPolicy: config?.uniqueKeys
48
- ? { uniqueKeys: config.uniqueKeys }
49
- : undefined
50
- }))
51
- )
38
+ const respBytes = (
39
+ resp: { diagnostics?: { clientSideRequestStatistics?: { totalResponsePayloadLengthInBytes?: number } } }
40
+ ) => resp.diagnostics?.clientSideRequestStatistics?.totalResponsePayloadLengthInBytes ?? 0
52
41
 
53
- const mainPartitionKey = config?.partitionValue() ?? "primary"
42
+ const annotateFeed = (resp: {
43
+ resources: readonly unknown[]
44
+ requestCharge?: number
45
+ diagnostics?: { clientSideRequestStatistics?: { totalResponsePayloadLengthInBytes?: number } }
46
+ }) =>
47
+ annotateCosmosResponse({
48
+ requestCharge: resp.requestCharge,
49
+ returnedRows: resp.resources.length,
50
+ responseBytes: respBytes(resp)
51
+ })
54
52
 
55
- const defaultValues = config?.defaultValues ?? {}
56
- const container = db.container(containerId)
57
- const bulk = container.items.bulk.bind(container.items)
58
- const execBatch = container.items.batch.bind(container.items)
59
- // TODO: move the marker to a separate container and get rid of the checks on every query
60
- // then need to clean up the actual data.. perhaps first do with a config toggle to prescribe to it.
61
- const importedMarkerId = containerId
53
+ const annotateItem = (resp: {
54
+ requestCharge?: number
55
+ statusCode?: number
56
+ diagnostics?: { clientSideRequestStatistics?: { totalResponsePayloadLengthInBytes?: number } }
57
+ }) =>
58
+ annotateCosmosResponse({
59
+ requestCharge: resp.requestCharge,
60
+ statusCode: resp.statusCode,
61
+ responseBytes: respBytes(resp)
62
+ })
62
63
 
63
- const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
64
- Effect
65
- .gen(function*() {
66
- // TODO: disable batching if need atomicity
67
- // we delay and batch to keep low amount of RUs
68
- const b = [...items]
69
- .map(
70
- (x) =>
71
- [
72
- x,
73
- Option.match(Option.fromNullishOr(x._etag), {
74
- onNone: () =>
75
- dropUndefinedT({
76
- operationType: "Create" as const,
77
- resourceBody: {
78
- ...Struct.omit(x, ["_etag", idKey]),
79
- id: x[idKey],
80
- _partitionKey: config?.partitionValue(x)
81
- }
82
- // don't use this or we get an error that the request and some item partition key dont match - makese no sense
83
- // partitionKey: config?.partitionValue(x)
84
- }),
85
- onSome: (eTag) =>
86
- dropUndefinedT({
87
- operationType: "Replace" as const,
88
- id: x[idKey],
89
- resourceBody: {
90
- ...Struct.omit(x, ["_etag", idKey]),
91
- id: x[idKey],
92
- _partitionKey: config?.partitionValue(x)
93
- },
94
- ifMatch: eTag
95
- // don't use this or we get an error that the request and some item partition key dont match - makese no sense
96
- // partitionKey: config?.partitionValue(x)
97
- })
98
- })
99
- ] as const
64
+ const makeCosmosStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
65
+ const { db } = yield* CosmosClient
66
+ return {
67
+ make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
68
+ name: string,
69
+ idKey: IdKey,
70
+ seed?: Effect.Effect<Iterable<Encoded>, E, R>,
71
+ config?: StoreConfig<Encoded>
72
+ ) {
73
+ const mapId = makeMapId<IdKey, Encoded>(idKey)
74
+ const mapReverseId = makeReverseMapId<IdKey, Encoded>(idKey)
75
+ type PM = PersistenceModelType<Encoded>
76
+ type PMCosmos = PersistenceModelType<Omit<Encoded, IdKey> & { id: string }>
77
+ const containerId = `${prefix}${name}`
78
+ yield* Effect.promise(() =>
79
+ db.containers.createIfNotExists(dropUndefinedT({
80
+ id: containerId,
81
+ uniqueKeyPolicy: config?.uniqueKeys
82
+ ? { uniqueKeys: config.uniqueKeys }
83
+ : undefined,
84
+ partitionKey: {
85
+ paths: ["/_partitionKey"],
86
+ version: 2 // support large partitionkeys so that the hash is not based on just the first 100 bytes!
87
+ }
88
+ }))
89
+ )
90
+
91
+ const basePartitionKey = config?.partitionValue() ?? "primary"
92
+ const nsPrefix = (ns: string) => ns === "primary" ? "" : `${ns}::`
93
+ const nsPartitionValue = (ns: string, e?: Encoded) => {
94
+ const base = config?.partitionValue(e) ?? "primary"
95
+ return `${nsPrefix(ns)}${base}`
96
+ }
97
+ const nsBasePartitionKey = (ns: string) => `${nsPrefix(ns)}${basePartitionKey}`
98
+ const resolveNamespace = !config?.allowNamespace
99
+ ? Effect.succeed("primary")
100
+ : storeId.asEffect().pipe(Effect.map((namespace) => {
101
+ if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
102
+ throw new Error(`Namespace ${namespace} not allowed!`)
103
+ }
104
+ return namespace
105
+ }))
106
+
107
+ const defaultValues = config?.defaultValues ?? {}
108
+ const container = db.container(containerId)
109
+ const bulk = container.items.bulk.bind(container.items)
110
+ const execBatch = container.items.batch.bind(container.items)
111
+ // TODO: move the marker to a separate container and get rid of the checks on every query
112
+ // then need to clean up the actual data.. perhaps first do with a config toggle to prescribe to it.
113
+ const importedMarkerId = containerId
114
+
115
+ const ctx = yield* Effect.context<R>()
116
+ const seedCache = new Map<string, Effect.Effect<void>>()
117
+ const makeSeedEffect = (ns: string) => {
118
+ const markerId = ns === "primary" ? importedMarkerId : `${importedMarkerId}::${ns}`
119
+ return Effect
120
+ .promise(() =>
121
+ container
122
+ .item(markerId, markerId)
123
+ .read<{ id: string }>()
124
+ .then(({ resource }) => Option.fromNullishOr(resource))
125
+ )
126
+ .pipe(
127
+ Effect.flatMap((marker) => {
128
+ if (Option.isSome(marker)) return Effect.void
129
+ return InfraLogger.logInfo(`Creating mock data for ${name} (namespace: ${ns})`).pipe(
130
+ Effect.andThen(seed!),
131
+ Effect.flatMap((m) =>
132
+ Effect.flatMapOption(
133
+ Effect.succeed(toNonEmptyArray([...m])),
134
+ (a) => bulkSetInternal(a, ns).pipe(Effect.orDie)
100
135
  )
101
- const batches = Array.chunksOf(b, config?.maxBulkSize ?? 10)
136
+ ),
137
+ Effect.andThen(
138
+ Effect.promise(() =>
139
+ container.items.create({
140
+ _partitionKey: markerId,
141
+ id: markerId,
142
+ ttl: -1
143
+ })
144
+ )
145
+ ),
146
+ Effect.provide(ctx),
147
+ Effect.orDie
148
+ )
149
+ }),
150
+ Effect.withLogSpan(`Cosmos.seedCheck ${name} in ${ns} [effect-app/infra/Store]`),
151
+ annotateDb({
152
+ operation: "seed",
153
+ system: "cosmosdb",
154
+ collection: containerId,
155
+ namespace: ns,
156
+ entity: name
157
+ })
158
+ )
159
+ }
160
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
161
+ if (!seed) return
162
+ let cached = seedCache.get(ns)
163
+ if (!cached) {
164
+ cached = yield* Effect.cached(Effect.uninterruptible(makeSeedEffect(ns)))
165
+ seedCache.set(ns, cached)
166
+ }
167
+ yield* cached
168
+ })
169
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
170
+ Effect
171
+ .gen(function*() {
172
+ // TODO: disable batching if need atomicity
173
+ // we delay and batch to keep low amount of RUs
174
+ const b = [...items]
175
+ .map(
176
+ (x) =>
177
+ [
178
+ x,
179
+ Option.match(Option.fromNullishOr(x._etag), {
180
+ onNone: () =>
181
+ dropUndefinedT({
182
+ operationType: "Create" as const,
183
+ resourceBody: {
184
+ ...Struct.omit(x, ["_etag", idKey]),
185
+ id: x[idKey],
186
+ _partitionKey: nsPartitionValue(ns, x)
187
+ }
188
+ // don't use this or we get an error that the request and some item partition key dont match - makese no sense
189
+ // partitionKey: config?.partitionValue(x)
190
+ }),
191
+ onSome: (eTag) =>
192
+ dropUndefinedT({
193
+ operationType: "Replace" as const,
194
+ id: x[idKey],
195
+ resourceBody: {
196
+ ...Struct.omit(x, ["_etag", idKey]),
197
+ id: x[idKey],
198
+ _partitionKey: nsPartitionValue(ns, x)
199
+ },
200
+ ifMatch: eTag
201
+ // don't use this or we get an error that the request and some item partition key dont match - makese no sense
202
+ // partitionKey: config?.partitionValue(x)
203
+ })
204
+ })
205
+ ] as const
206
+ )
207
+ const batches = Array.chunksOf(b, config?.maxBulkSize ?? 10)
102
208
 
103
- const batchResult = yield* Effect.forEach(
104
- batches
105
- .map((x, i) => [i, x] as const),
106
- ([i, batch]) =>
209
+ const batchResult = yield* Effect.forEach(
210
+ batches
211
+ .map((x, i) => [i, x] as const),
212
+ ([i, batch]) =>
213
+ Effect
214
+ .promise(() => bulk(batch.map(([, op]) => op)))
215
+ .pipe(
107
216
  Effect
108
- .promise(() => bulk(batch.map(([, op]) => op)))
109
- .pipe(
110
- Effect
111
- .delay(Duration.millis(i === 0 ? 0 : 1100)),
112
- Effect
113
- .flatMap((responses) =>
114
- Effect.gen(function*() {
115
- const r = responses.find((x) =>
116
- x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409
117
- )
118
- if (r) {
119
- return yield* Effect.fail(
120
- new OptimisticConcurrencyException(
121
- {
122
- type: name,
123
- id: JSON.stringify(r.resourceBody?.["id"]),
124
- code: r.statusCode,
125
- raw: responses
126
- }
127
- )
128
- )
129
- }
130
- const r2 = responses.find(
131
- (x) => x.statusCode !== 424 && (x.statusCode > 299 || x.statusCode < 200)
217
+ .delay(Duration.millis(i === 0 ? 0 : 150)),
218
+ Effect
219
+ .flatMap((responses) =>
220
+ Effect.gen(function*() {
221
+ const r = responses.find((x) =>
222
+ x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409
223
+ )
224
+ if (r) {
225
+ return yield* Effect.fail(
226
+ new OptimisticConcurrencyException(
227
+ {
228
+ type: name,
229
+ id: JSON.stringify(r.resourceBody?.["id"]),
230
+ code: r.statusCode,
231
+ raw: responses
232
+ }
132
233
  )
133
- if (r2) {
134
- return yield* Effect.die(
135
- new CosmosDbOperationError(
136
- "not able to update records: " + r2.statusCode,
137
- responses
138
- )
139
- )
140
- }
141
- const r3 = responses.find(
142
- (x) => x.statusCode > 299 || x.statusCode < 200
234
+ )
235
+ }
236
+ const r2 = responses.find(
237
+ (x) => x.statusCode !== 424 && (x.statusCode > 299 || x.statusCode < 200)
238
+ )
239
+ if (r2) {
240
+ return yield* Effect.die(
241
+ new CosmosDbOperationError(
242
+ "not able to update records: " + r2.statusCode,
243
+ responses
143
244
  )
144
- if (r3) {
145
- return yield* Effect.die(
146
- new CosmosDbOperationError(
147
- "not able to update records: " + r3.statusCode,
148
- responses
149
- )
150
- )
151
- }
152
- return batch.map(([e], i) => ({
153
- ...e,
154
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
- _etag: responses[i]!.eTag
156
- }))
157
- })
245
+ )
246
+ }
247
+ const r3 = responses.find(
248
+ (x) => x.statusCode > 299 || x.statusCode < 200
158
249
  )
250
+ if (r3) {
251
+ return yield* Effect.die(
252
+ new CosmosDbOperationError(
253
+ "not able to update records: " + r3.statusCode,
254
+ responses
255
+ )
256
+ )
257
+ }
258
+ return batch.map(([e], i) => ({
259
+ ...e,
260
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
261
+ _etag: responses[i]!.eTag
262
+ }))
263
+ })
159
264
  )
160
- )
265
+ )
266
+ )
161
267
 
162
- return batchResult.flat() as unknown as NonEmptyReadonlyArray<Encoded>
163
- })
164
- .pipe(Effect.withSpan("Cosmos.bulkSet [effect-app/infra/Store]", {
165
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
166
- }, { captureStackTrace: false }))
268
+ return batchResult.flat() as unknown as NonEmptyReadonlyArray<Encoded>
269
+ })
270
+ .pipe(
271
+ annotateDb({
272
+ operation: "bulkSet",
273
+ system: "cosmosdb",
274
+ collection: containerId,
275
+ namespace: ns,
276
+ entity: name
277
+ })
278
+ )
279
+
280
+ const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
281
+ resolveNamespace.pipe(Effect.flatMap((ns) => bulkSetInternal(items, ns)))
167
282
 
168
- const batchSet = (items: NonEmptyReadonlyArray<PM>) => {
169
- return Effect
283
+ const batchSet = (items: NonEmptyReadonlyArray<PM>) => {
284
+ return resolveNamespace
285
+ .pipe(Effect.flatMap((ns) =>
286
+ Effect
170
287
  .suspend(() => {
171
288
  const batch = [...items].map(
172
289
  (x) =>
@@ -178,7 +295,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
178
295
  resourceBody: {
179
296
  ...Struct.omit(x, ["_etag", idKey]),
180
297
  id: x[idKey],
181
- _partitionKey: config?.partitionValue(x)
298
+ _partitionKey: nsPartitionValue(ns, x)
182
299
  }
183
300
  // don't use this or we get an error that the request and some item partition key dont match - makese no sense
184
301
  // partitionKey: config?.partitionValue(x)
@@ -189,7 +306,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
189
306
  resourceBody: {
190
307
  ...Struct.omit(x, ["_etag", idKey]),
191
308
  id: x[idKey],
192
- _partitionKey: config?.partitionValue(x)
309
+ _partitionKey: nsPartitionValue(ns, x)
193
310
  },
194
311
  // don't use this or we get an error that the request and some item partition key dont match - makese no sense
195
312
  // partitionKey: config?.partitionValue(x)
@@ -225,38 +342,51 @@ function makeCosmosStore({ prefix }: StorageConfig) {
225
342
  })) as unknown as NonEmptyReadonlyArray<Encoded>
226
343
  })))
227
344
  })
228
- .pipe(Effect
229
- .withSpan("Cosmos.batchSet [effect-app/infra/Store]", {
230
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
231
- }, { captureStackTrace: false }))
232
- }
345
+ .pipe(annotateDb({
346
+ operation: "batchSet",
347
+ system: "cosmosdb",
348
+ collection: containerId,
349
+ namespace: ns,
350
+ entity: name
351
+ }))
352
+ ))
353
+ }
233
354
 
234
- const s: Store<IdKey, Encoded> = {
235
- queryRaw: <Out>(query: RawQuery<Encoded, Out>) =>
236
- Effect
237
- .sync(() => query.cosmos({ name }))
238
- .pipe(
239
- Effect.tap((q) => logQuery(q)),
240
- Effect.flatMap((q) =>
241
- Effect.promise(() =>
242
- container
243
- .items
244
- .query<Out>(q, { partitionKey: mainPartitionKey })
245
- .fetchAll()
246
- .then(({ resources }) =>
247
- resources.map(
248
- (_) => ({ ...defaultValues, ...mapReverseId(_ as any) }) as Out
249
- )
250
- )
355
+ const s: Store<IdKey, Encoded> = {
356
+ seedNamespace: (ns) => seedNamespace(ns),
357
+
358
+ queryRaw: <Out>(query: RawQuery<Encoded, Out>) =>
359
+ Effect
360
+ .all({ q: Effect.sync(() => query.cosmos({ name })), ns: resolveNamespace })
361
+ .pipe(
362
+ Effect.tap(({ q }) => logQuery(q)),
363
+ Effect.flatMap(({ ns, q }) =>
364
+ Effect
365
+ .gen(function*() {
366
+ const response = yield* Effect.promise(() =>
367
+ container.items.query<Out>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
251
368
  )
252
- ),
253
- Effect
254
- .withSpan("Cosmos.queryRaw [effect-app/infra/Store]", {
255
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
256
- }, { captureStackTrace: false })
257
- ),
258
- batchRemove: (ids, partitionKey?: string) =>
259
- Effect.promise(() =>
369
+ yield* annotateFeed(response)
370
+ return response.resources.map(
371
+ (_) => ({ ...defaultValues, ...mapReverseId(_ as any) }) as Out
372
+ )
373
+ })
374
+ .pipe(
375
+ annotateDb({
376
+ operation: "queryRaw",
377
+ system: "cosmosdb",
378
+ collection: containerId,
379
+ namespace: ns,
380
+ entity: name,
381
+ query: q.query
382
+ })
383
+ )
384
+ )
385
+ ),
386
+ batchRemove: (ids, partitionKey?: string) =>
387
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
388
+ Effect
389
+ .promise(() =>
260
390
  execBatch(
261
391
  mutable(ids.map((id) =>
262
392
  dropUndefinedT({
@@ -266,208 +396,219 @@ function makeCosmosStore({ prefix }: StorageConfig) {
266
396
  // partitionKey: config?.partitionValue({ [idKey]: id } as Encoded)
267
397
  })
268
398
  )),
269
- partitionKey ?? mainPartitionKey
399
+ partitionKey ?? nsBasePartitionKey(ns)
270
400
  )
271
- ),
272
- all: Effect
273
- .sync(() => ({
274
- query: `SELECT * FROM ${name}`,
275
- parameters: []
276
- }))
401
+ )
277
402
  .pipe(
278
- Effect.tap((q) => logQuery(q)),
279
- Effect.flatMap((q) =>
280
- Effect.promise(() =>
281
- container
282
- .items
283
- .query<PMCosmos>(q, { partitionKey: mainPartitionKey })
284
- .fetchAll()
285
- .then(({ resources }) =>
286
- resources.map(
287
- (_) => ({ ...defaultValues, ...mapReverseId(_) })
288
- )
289
- )
290
- )
291
- ),
292
- Effect
293
- .withSpan("Cosmos.all [effect-app/infra/Store]", {
294
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
295
- }, { captureStackTrace: false })
296
- ),
297
- /**
298
- * May return duplicate results for "join_find", when matching more than once.
299
- */
300
- filter: <U extends keyof Encoded = never>(
301
- f: FilterArgs<Encoded, U>
302
- ) => {
303
- const skip = f?.skip
304
- const limit = f?.limit
305
- const filter = f.filter
306
- type M = U extends undefined ? Encoded : Pick<Encoded, U>
307
- return Effect
308
- .sync(() =>
309
- buildWhereCosmosQuery3(
310
- idKey,
311
- filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
312
- name,
313
- defaultValues,
314
- f.select as NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }> | undefined,
315
- f.order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
316
- skip,
317
- limit
403
+ annotateDb({
404
+ operation: "batchRemove",
405
+ system: "cosmosdb",
406
+ collection: containerId,
407
+ namespace: ns,
408
+ entity: name
409
+ })
410
+ )
411
+ )),
412
+ all: Effect
413
+ .all({
414
+ q: Effect.sync(() => ({
415
+ query: `SELECT * FROM ${name}`,
416
+ parameters: []
417
+ })),
418
+ ns: resolveNamespace
419
+ })
420
+ .pipe(
421
+ Effect.tap(({ q }) => logQuery(q)),
422
+ Effect.flatMap(({ ns, q }) =>
423
+ Effect
424
+ .gen(function*() {
425
+ const response = yield* Effect.promise(() =>
426
+ container.items.query<PMCosmos>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
318
427
  )
319
- )
428
+ yield* annotateFeed(response)
429
+ return response.resources.map((_) => ({ ...defaultValues, ...mapReverseId(_) }))
430
+ })
320
431
  .pipe(
321
- Effect.tap((q) => logQuery(q)),
432
+ annotateDb({
433
+ operation: "all",
434
+ system: "cosmosdb",
435
+ collection: containerId,
436
+ namespace: ns,
437
+ entity: name,
438
+ query: q.query
439
+ })
440
+ )
441
+ )
442
+ ),
443
+ /**
444
+ * May return duplicate results for "join_find", when matching more than once.
445
+ */
446
+ filter: <U extends keyof Encoded = never>(
447
+ f: FilterArgs<Encoded, U>
448
+ ) => {
449
+ const skip = f?.skip
450
+ const limit = f?.limit
451
+ const filter = f.filter
452
+ type M = U extends undefined ? Encoded : Pick<Encoded, U>
453
+ return Effect
454
+ .all({
455
+ q: Effect.sync(() =>
456
+ buildWhereCosmosQuery3(
457
+ idKey,
458
+ filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
459
+ name,
460
+ defaultValues,
461
+ f.select as
462
+ | NonEmptyReadonlyArray<
463
+ string | {
464
+ key: string
465
+ subKeys: readonly string[]
466
+ } | {
467
+ key: string
468
+ computed: ComputedProjectionIrExpression
469
+ }
470
+ >
471
+ | undefined,
472
+ f.order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
473
+ skip,
474
+ limit
475
+ )
476
+ ),
477
+ ns: resolveNamespace
478
+ })
479
+ .pipe(
480
+ Effect.tap(({ q }) => logQuery(q)),
481
+ Effect
482
+ .flatMap(({ ns, q }) =>
322
483
  Effect
323
- .flatMap((q) =>
324
- Effect.promise(() =>
325
- f.select
326
- ? container
327
- .items
328
- .query<M>(q, { partitionKey: mainPartitionKey })
329
- .fetchAll()
330
- .then(({ resources }) =>
331
- resources.map((_) => ({
332
- ...pipe(
333
- defaultValues,
334
- Struct.pick(f.select!.filter((_) => typeof _ === "string") as never[])
335
- ),
336
- ...mapReverseId(_ as any)
337
- }))
338
- )
339
- : container
340
- .items
341
- .query<{ f: M }>(q, { partitionKey: mainPartitionKey })
342
- .fetchAll()
343
- .then(({ resources }) =>
344
- resources.map(({ f }) => ({ ...defaultValues, ...mapReverseId(f as any) }) as any)
345
- )
484
+ .gen(function*() {
485
+ if (f.select) {
486
+ const response = yield* Effect.promise(() =>
487
+ container.items.query<M>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
488
+ )
489
+ yield* annotateFeed(response)
490
+ return response.resources.map((_) => ({
491
+ ...pipe(
492
+ defaultValues,
493
+ Struct.pick(f.select!.filter((_) => typeof _ === "string") as never[])
494
+ ),
495
+ ...mapReverseId(_ as any)
496
+ }))
497
+ }
498
+ const response = yield* Effect.promise(() =>
499
+ container.items.query<{ f: M }>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
346
500
  )
501
+ yield* annotateFeed(response)
502
+ return response.resources.map(({ f }) => ({ ...defaultValues, ...mapReverseId(f as any) }) as any)
503
+ })
504
+ .pipe(
505
+ annotateDb({
506
+ operation: "filter",
507
+ system: "cosmosdb",
508
+ collection: containerId,
509
+ namespace: ns,
510
+ entity: name,
511
+ query: q.query
512
+ })
347
513
  )
348
514
  )
349
- .pipe(
350
- Effect.withSpan("Cosmos.filter [effect-app/infra/Store]", {
351
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
352
- }, { captureStackTrace: false })
353
- )
354
- },
355
- find: (id) =>
356
- Effect
357
- .promise(() =>
515
+ )
516
+ },
517
+ find: (id) =>
518
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
519
+ Effect
520
+ .gen(function*() {
521
+ const response = yield* Effect.promise(() =>
358
522
  container
359
- .item(id, config?.partitionValue({ [idKey]: id } as Encoded))
523
+ .item(id, nsPartitionValue(ns, { [idKey]: id } as Encoded))
360
524
  .read<Encoded>()
361
- .then(({ resource }) =>
362
- Option.fromNullishOr(resource).pipe(Option.map((_) => ({ ...defaultValues, ...mapReverseId(_) })))
363
- )
364
525
  )
365
- .pipe(Effect
366
- .withSpan("Cosmos.find [effect-app/infra/Store]", {
367
- attributes: {
368
- "repository.container_id": containerId,
369
- "repository.model_name": name,
370
- partitionValue: config?.partitionValue({ [idKey]: id } as Encoded),
371
- id
372
- }
373
- }, { captureStackTrace: false })),
374
- set: (e) =>
375
- Option
376
- .match(
377
- Option
378
- .fromNullishOr(e._etag),
379
- {
380
- onNone: () =>
381
- Effect.promise(() =>
382
- container.items.create({
383
- ...mapId(e),
384
- _partitionKey: config?.partitionValue(e)
385
- })
386
- ),
387
- onSome: (eTag) =>
388
- Effect.promise(() =>
389
- container.item(e[idKey], config?.partitionValue(e)).replace(
390
- { ...mapId(e), _partitionKey: config?.partitionValue(e) },
391
- {
392
- accessCondition: {
393
- type: "IfMatch",
394
- condition: eTag
395
- }
526
+ yield* annotateItem(response)
527
+ return Option.fromNullishOr(response.resource).pipe(
528
+ Option.map((_) => ({ ...defaultValues, ...mapReverseId(_) }))
529
+ )
530
+ })
531
+ .pipe(annotateDb({
532
+ operation: "find",
533
+ system: "cosmosdb",
534
+ collection: containerId,
535
+ namespace: ns,
536
+ entity: name,
537
+ extra: {
538
+ "azure.cosmosdb.operation.partition_key": nsPartitionValue(ns, { [idKey]: id } as Encoded),
539
+ "app.entity.id": id
540
+ }
541
+ }))
542
+ )),
543
+ set: (e) =>
544
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
545
+ Option
546
+ .match(
547
+ Option
548
+ .fromNullishOr(e._etag),
549
+ {
550
+ onNone: () =>
551
+ Effect.promise(() =>
552
+ container.items.create({
553
+ ...mapId(e),
554
+ _partitionKey: nsPartitionValue(ns, e)
555
+ })
556
+ ),
557
+ onSome: (eTag) =>
558
+ Effect.promise(() =>
559
+ container.item(e[idKey], nsPartitionValue(ns, e)).replace(
560
+ { ...mapId(e), _partitionKey: nsPartitionValue(ns, e) },
561
+ {
562
+ accessCondition: {
563
+ type: "IfMatch",
564
+ condition: eTag
396
565
  }
397
- )
566
+ }
398
567
  )
399
- }
400
- )
401
- .pipe(
402
- Effect
403
- .flatMap((x) => {
404
- if (x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409) {
405
- return Effect.fail(
406
- new OptimisticConcurrencyException({ type: name, id: e[idKey], code: x.statusCode })
407
- )
408
- }
409
- if (x.statusCode > 299 || x.statusCode < 200) {
410
- return Effect.die(
411
- new CosmosDbOperationError(
412
- "not able to update record: " + x.statusCode
413
- )
568
+ )
569
+ }
570
+ )
571
+ .pipe(
572
+ Effect
573
+ .flatMap((x) => {
574
+ if (x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409) {
575
+ return Effect.fail(
576
+ new OptimisticConcurrencyException({ type: name, id: e[idKey], code: x.statusCode })
577
+ )
578
+ }
579
+ if (x.statusCode > 299 || x.statusCode < 200) {
580
+ return Effect.die(
581
+ new CosmosDbOperationError(
582
+ "not able to update record: " + x.statusCode
414
583
  )
415
- }
416
- return Effect.sync(() => ({
417
- ...e,
418
- _etag: x.etag
419
- }))
420
- }),
421
- Effect
422
- .withSpan("Cosmos.set [effect-app/infra/Store]", {
423
- attributes: {
424
- "repository.container_id": containerId,
425
- "repository.model_name": name,
426
- id: e[idKey]
427
- }
428
- }, { captureStackTrace: false })
429
- ),
430
- batchSet,
431
- bulkSet
432
- }
584
+ )
585
+ }
586
+ return Effect.sync(() => ({
587
+ ...e,
588
+ _etag: x.etag
589
+ }))
590
+ }),
591
+ annotateDb({
592
+ operation: "set",
593
+ system: "cosmosdb",
594
+ collection: containerId,
595
+ namespace: ns,
596
+ entity: name,
597
+ extra: { "app.entity.id": e[idKey] }
598
+ })
599
+ )
600
+ )),
601
+ batchSet,
602
+ bulkSet
603
+ }
433
604
 
434
- // handle mock data
435
- const marker = yield* Effect.promise(() =>
436
- container
437
- .item(importedMarkerId, importedMarkerId)
438
- .read<{ id: string }>()
439
- .then(({ resource }) => Option.fromNullishOr(resource))
440
- )
605
+ // Eagerly seed primary namespace on initialization
606
+ yield* seedNamespace("primary")
441
607
 
442
- if (!Option.isSome(marker)) {
443
- yield* InfraLogger.logInfo("Creating mock data for " + name)
444
- if (seed) {
445
- const m = yield* seed
446
- yield* Effect.flatMapOption(
447
- Effect.succeed(toNonEmptyArray([...m])),
448
- (a) =>
449
- s.bulkSet(a).pipe(
450
- Effect.orDie,
451
- Effect
452
- // we delay extra here, so that initial creation between Companies/POs also have an interval between them.
453
- .delay(Duration.millis(1100))
454
- )
455
- )
456
- }
457
- // Mark as imported
458
- yield* Effect.promise(() =>
459
- container.items.create({
460
- _partitionKey: importedMarkerId,
461
- id: importedMarkerId,
462
- ttl: -1
463
- })
464
- )
465
- }
466
- return s
467
- })
468
- }
469
- })
470
- }
608
+ return s
609
+ })
610
+ }
611
+ })
471
612
 
472
613
  export function CosmosStoreLayer(cfg: StorageConfig) {
473
614
  return StoreMaker