@effect-app/infra 4.0.0-beta.12 → 4.0.0-beta.120

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