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

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