@effect-app/infra 4.0.0-beta.11 → 4.0.0-beta.111

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