@effect-app/infra 4.0.0-beta.9 → 4.0.0-beta.90

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 (177) hide show
  1. package/CHANGELOG.md +589 -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/Sendgrid.js +1 -1
  6. package/dist/Emailer/service.d.ts +3 -3
  7. package/dist/Emailer/service.d.ts.map +1 -1
  8. package/dist/Emailer/service.js +3 -3
  9. package/dist/MainFiberSet.d.ts +2 -2
  10. package/dist/MainFiberSet.d.ts.map +1 -1
  11. package/dist/MainFiberSet.js +3 -3
  12. package/dist/Model/Repository/internal/internal.d.ts +3 -3
  13. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  14. package/dist/Model/Repository/internal/internal.js +11 -7
  15. package/dist/Model/Repository/makeRepo.d.ts +2 -2
  16. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  17. package/dist/Model/Repository/makeRepo.js +1 -1
  18. package/dist/Model/Repository/validation.d.ts +5 -4
  19. package/dist/Model/Repository/validation.d.ts.map +1 -1
  20. package/dist/Model/query/dsl.d.ts +9 -9
  21. package/dist/Operations.d.ts +2 -2
  22. package/dist/Operations.d.ts.map +1 -1
  23. package/dist/Operations.js +3 -3
  24. package/dist/OperationsRepo.d.ts +2 -2
  25. package/dist/OperationsRepo.d.ts.map +1 -1
  26. package/dist/OperationsRepo.js +3 -3
  27. package/dist/QueueMaker/SQLQueue.d.ts +3 -5
  28. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  29. package/dist/QueueMaker/SQLQueue.js +9 -7
  30. package/dist/QueueMaker/errors.d.ts +1 -1
  31. package/dist/QueueMaker/errors.d.ts.map +1 -1
  32. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  33. package/dist/QueueMaker/memQueue.js +10 -9
  34. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  35. package/dist/QueueMaker/sbqueue.js +11 -9
  36. package/dist/RequestContext.d.ts +19 -14
  37. package/dist/RequestContext.d.ts.map +1 -1
  38. package/dist/RequestContext.js +5 -5
  39. package/dist/RequestFiberSet.d.ts +2 -2
  40. package/dist/RequestFiberSet.d.ts.map +1 -1
  41. package/dist/RequestFiberSet.js +5 -5
  42. package/dist/Store/ContextMapContainer.d.ts +14 -3
  43. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  44. package/dist/Store/ContextMapContainer.js +64 -3
  45. package/dist/Store/Cosmos.d.ts.map +1 -1
  46. package/dist/Store/Cosmos.js +91 -56
  47. package/dist/Store/Disk.d.ts.map +1 -1
  48. package/dist/Store/Disk.js +3 -4
  49. package/dist/Store/Memory.d.ts +2 -2
  50. package/dist/Store/Memory.d.ts.map +1 -1
  51. package/dist/Store/Memory.js +4 -4
  52. package/dist/Store/SQL/Pg.d.ts +4 -0
  53. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  54. package/dist/Store/SQL/Pg.js +186 -0
  55. package/dist/Store/SQL/query.d.ts +36 -0
  56. package/dist/Store/SQL/query.d.ts.map +1 -0
  57. package/dist/Store/SQL/query.js +385 -0
  58. package/dist/Store/SQL.d.ts +11 -0
  59. package/dist/Store/SQL.d.ts.map +1 -0
  60. package/dist/Store/SQL.js +212 -0
  61. package/dist/Store/index.d.ts +1 -1
  62. package/dist/Store/index.d.ts.map +1 -1
  63. package/dist/Store/index.js +11 -1
  64. package/dist/Store/service.d.ts +8 -5
  65. package/dist/Store/service.d.ts.map +1 -1
  66. package/dist/Store/service.js +14 -6
  67. package/dist/adapters/SQL/Model.d.ts +2 -5
  68. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  69. package/dist/adapters/SQL/Model.js +21 -13
  70. package/dist/adapters/ServiceBus.d.ts +6 -6
  71. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  72. package/dist/adapters/ServiceBus.js +9 -9
  73. package/dist/adapters/cosmos-client.d.ts +2 -2
  74. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  75. package/dist/adapters/cosmos-client.js +3 -3
  76. package/dist/adapters/logger.d.ts.map +1 -1
  77. package/dist/adapters/memQueue.d.ts +2 -2
  78. package/dist/adapters/memQueue.d.ts.map +1 -1
  79. package/dist/adapters/memQueue.js +3 -3
  80. package/dist/adapters/mongo-client.d.ts +2 -2
  81. package/dist/adapters/mongo-client.d.ts.map +1 -1
  82. package/dist/adapters/mongo-client.js +3 -3
  83. package/dist/adapters/redis-client.d.ts +3 -3
  84. package/dist/adapters/redis-client.d.ts.map +1 -1
  85. package/dist/adapters/redis-client.js +3 -3
  86. package/dist/api/ContextProvider.d.ts +6 -6
  87. package/dist/api/ContextProvider.d.ts.map +1 -1
  88. package/dist/api/ContextProvider.js +6 -6
  89. package/dist/api/internal/RequestContextMiddleware.d.ts +1 -1
  90. package/dist/api/internal/auth.d.ts +1 -1
  91. package/dist/api/internal/events.d.ts +2 -2
  92. package/dist/api/internal/events.d.ts.map +1 -1
  93. package/dist/api/internal/events.js +7 -5
  94. package/dist/api/layerUtils.d.ts +5 -5
  95. package/dist/api/layerUtils.d.ts.map +1 -1
  96. package/dist/api/layerUtils.js +5 -5
  97. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  98. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  99. package/dist/api/routing/middleware/middleware.d.ts +35 -1
  100. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  101. package/dist/api/routing/middleware/middleware.js +39 -1
  102. package/dist/api/routing/schema/jwt.d.ts +1 -1
  103. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  104. package/dist/api/routing/schema/jwt.js +1 -1
  105. package/dist/api/routing.d.ts +1 -5
  106. package/dist/api/routing.d.ts.map +1 -1
  107. package/dist/api/routing.js +3 -2
  108. package/dist/api/setupRequest.d.ts +6 -3
  109. package/dist/api/setupRequest.d.ts.map +1 -1
  110. package/dist/api/setupRequest.js +11 -6
  111. package/dist/errorReporter.d.ts +1 -1
  112. package/dist/errorReporter.d.ts.map +1 -1
  113. package/dist/errorReporter.js +1 -1
  114. package/dist/fileUtil.js +1 -1
  115. package/dist/logger.d.ts.map +1 -1
  116. package/dist/rateLimit.js +1 -1
  117. package/examples/query.ts +29 -25
  118. package/package.json +32 -18
  119. package/src/CUPS.ts +2 -2
  120. package/src/Emailer/Sendgrid.ts +1 -1
  121. package/src/Emailer/service.ts +2 -2
  122. package/src/MainFiberSet.ts +2 -2
  123. package/src/Model/Repository/internal/internal.ts +11 -8
  124. package/src/Model/Repository/makeRepo.ts +2 -2
  125. package/src/Operations.ts +2 -2
  126. package/src/OperationsRepo.ts +2 -2
  127. package/src/QueueMaker/SQLQueue.ts +10 -10
  128. package/src/QueueMaker/memQueue.ts +41 -42
  129. package/src/QueueMaker/sbqueue.ts +65 -62
  130. package/src/RequestContext.ts +4 -4
  131. package/src/RequestFiberSet.ts +4 -4
  132. package/src/Store/ContextMapContainer.ts +98 -2
  133. package/src/Store/Cosmos.ts +273 -207
  134. package/src/Store/Disk.ts +2 -3
  135. package/src/Store/Memory.ts +4 -6
  136. package/src/Store/SQL/Pg.ts +328 -0
  137. package/src/Store/SQL/query.ts +430 -0
  138. package/src/Store/SQL.ts +357 -0
  139. package/src/Store/index.ts +10 -0
  140. package/src/Store/service.ts +16 -7
  141. package/src/adapters/SQL/Model.ts +76 -71
  142. package/src/adapters/ServiceBus.ts +8 -8
  143. package/src/adapters/cosmos-client.ts +2 -2
  144. package/src/adapters/memQueue.ts +2 -2
  145. package/src/adapters/mongo-client.ts +2 -2
  146. package/src/adapters/redis-client.ts +2 -2
  147. package/src/api/ContextProvider.ts +11 -11
  148. package/src/api/internal/events.ts +7 -6
  149. package/src/api/layerUtils.ts +8 -8
  150. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  151. package/src/api/routing/middleware/middleware.ts +43 -0
  152. package/src/api/routing/schema/jwt.ts +2 -3
  153. package/src/api/routing.ts +7 -6
  154. package/src/api/setupRequest.ts +27 -7
  155. package/src/errorReporter.ts +1 -1
  156. package/src/fileUtil.ts +1 -1
  157. package/src/rateLimit.ts +2 -2
  158. package/test/contextProvider.test.ts +5 -5
  159. package/test/controller.test.ts +12 -9
  160. package/test/dist/contextProvider.test.d.ts.map +1 -1
  161. package/test/dist/controller.test.d.ts.map +1 -1
  162. package/test/dist/fixtures.d.ts +18 -8
  163. package/test/dist/fixtures.d.ts.map +1 -1
  164. package/test/dist/fixtures.js +11 -9
  165. package/test/dist/query.test.d.ts.map +1 -1
  166. package/test/dist/rawQuery.test.d.ts.map +1 -1
  167. package/test/dist/requires.test.d.ts.map +1 -1
  168. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  169. package/test/dist/sql-store.test.d.ts.map +1 -0
  170. package/test/fixtures.ts +10 -8
  171. package/test/query.test.ts +160 -14
  172. package/test/rawQuery.test.ts +19 -17
  173. package/test/requires.test.ts +6 -5
  174. package/test/rpc-multi-middleware.test.ts +73 -4
  175. package/test/sql-store.test.ts +776 -0
  176. package/test/validateSample.test.ts +1 -1
  177. 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 =
@@ -50,7 +51,21 @@ function makeCosmosStore({ prefix }: StorageConfig) {
50
51
  }))
51
52
  )
52
53
 
53
- const mainPartitionKey = config?.partitionValue() ?? "primary"
54
+ const basePartitionKey = config?.partitionValue() ?? "primary"
55
+ const nsPrefix = (ns: string) => ns === "primary" ? "" : `${ns}::`
56
+ const nsPartitionValue = (ns: string, e?: Encoded) => {
57
+ const base = config?.partitionValue(e) ?? "primary"
58
+ return `${nsPrefix(ns)}${base}`
59
+ }
60
+ const resolveNamespace = !config?.allowNamespace
61
+ ? Effect.succeed("primary")
62
+ : storeId.asEffect().pipe(Effect.map((namespace) => {
63
+ if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
64
+ throw new Error(`Namespace ${namespace} not allowed!`)
65
+ }
66
+ return namespace
67
+ }))
68
+ const resolvePartitionKey = Effect.map(resolveNamespace, (ns) => `${nsPrefix(ns)}${basePartitionKey}`)
54
69
 
55
70
  const defaultValues = config?.defaultValues ?? {}
56
71
  const container = db.container(containerId)
@@ -60,7 +75,62 @@ function makeCosmosStore({ prefix }: StorageConfig) {
60
75
  // then need to clean up the actual data.. perhaps first do with a config toggle to prescribe to it.
61
76
  const importedMarkerId = containerId
62
77
 
63
- const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
78
+ const ctx = yield* Effect.context<R>()
79
+ const seedCache = new Map<string, Effect.Effect<void>>()
80
+ const makeSeedEffect = (ns: string) => {
81
+ const markerId = ns === "primary" ? importedMarkerId : `${importedMarkerId}::${ns}`
82
+ return Effect
83
+ .promise(() =>
84
+ container
85
+ .item(markerId, markerId)
86
+ .read<{ id: string }>()
87
+ .then(({ resource }) => Option.fromNullishOr(resource))
88
+ )
89
+ .pipe(
90
+ Effect.flatMap((marker) => {
91
+ if (Option.isSome(marker)) return Effect.void
92
+ return InfraLogger.logInfo(`Creating mock data for ${name} (namespace: ${ns})`).pipe(
93
+ Effect.andThen(seed!),
94
+ Effect.flatMap((m) =>
95
+ Effect.flatMapOption(
96
+ Effect.succeed(toNonEmptyArray([...m])),
97
+ (a) =>
98
+ bulkSetInternal(a, ns).pipe(
99
+ Effect.orDie,
100
+ Effect.delay(Duration.millis(1100))
101
+ )
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
+ )
118
+ }
119
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
120
+ if (!seed) return
121
+ let cached = seedCache.get(ns)
122
+ if (!cached) {
123
+ cached = yield* Effect.cached(makeSeedEffect(ns))
124
+ seedCache.set(ns, cached)
125
+ }
126
+ yield* cached
127
+ })
128
+ const resolveAndSeed = resolveNamespace.pipe(
129
+ Effect.tap((ns) => seedNamespace(ns))
130
+ )
131
+ const resolvePartitionKeyAndSeed = Effect.map(resolveAndSeed, (ns) => `${nsPrefix(ns)}${basePartitionKey}`)
132
+
133
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
64
134
  Effect
65
135
  .gen(function*() {
66
136
  // TODO: disable batching if need atomicity
@@ -77,7 +147,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
77
147
  resourceBody: {
78
148
  ...Struct.omit(x, ["_etag", idKey]),
79
149
  id: x[idKey],
80
- _partitionKey: config?.partitionValue(x)
150
+ _partitionKey: nsPartitionValue(ns, x)
81
151
  }
82
152
  // don't use this or we get an error that the request and some item partition key dont match - makese no sense
83
153
  // partitionKey: config?.partitionValue(x)
@@ -89,7 +159,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
89
159
  resourceBody: {
90
160
  ...Struct.omit(x, ["_etag", idKey]),
91
161
  id: x[idKey],
92
- _partitionKey: config?.partitionValue(x)
162
+ _partitionKey: nsPartitionValue(ns, x)
93
163
  },
94
164
  ifMatch: eTag
95
165
  // don't use this or we get an error that the request and some item partition key dont match - makese no sense
@@ -161,70 +231,79 @@ function makeCosmosStore({ prefix }: StorageConfig) {
161
231
 
162
232
  return batchResult.flat() as unknown as NonEmptyReadonlyArray<Encoded>
163
233
  })
164
- .pipe(Effect.withSpan("Cosmos.bulkSet [effect-app/infra/Store]", {
165
- attributes: { "repository.container_id": containerId, "repository.model_name": name }
166
- }, { captureStackTrace: false }))
234
+
235
+ const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
236
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
237
+ bulkSetInternal(items, ns).pipe(
238
+ Effect.withSpan("Cosmos.bulkSet [effect-app/infra/Store]", {
239
+ attributes: { "repository.container_id": containerId, "repository.model_name": name }
240
+ }, { captureStackTrace: false })
241
+ )
242
+ ))
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
- })
300
+ return batch.map(([e], i) => ({
301
+ ...e,
302
+ _etag: result[i]?.eTag
303
+ })) as unknown as NonEmptyReadonlyArray<Encoded>
304
+ })))
305
+ })
306
+ ))
228
307
  .pipe(Effect
229
308
  .withSpan("Cosmos.batchSet [effect-app/infra/Store]", {
230
309
  attributes: { "repository.container_id": containerId, "repository.model_name": name }
@@ -234,14 +313,14 @@ function makeCosmosStore({ prefix }: StorageConfig) {
234
313
  const s: Store<IdKey, Encoded> = {
235
314
  queryRaw: <Out>(query: RawQuery<Encoded, Out>) =>
236
315
  Effect
237
- .sync(() => query.cosmos({ name }))
316
+ .all({ q: Effect.sync(() => query.cosmos({ name })), pk: resolvePartitionKeyAndSeed })
238
317
  .pipe(
239
- Effect.tap((q) => logQuery(q)),
240
- Effect.flatMap((q) =>
318
+ Effect.tap(({ q }) => logQuery(q)),
319
+ Effect.flatMap(({ pk, q }) =>
241
320
  Effect.promise(() =>
242
321
  container
243
322
  .items
244
- .query<Out>(q, { partitionKey: mainPartitionKey })
323
+ .query<Out>(q, { partitionKey: pk })
245
324
  .fetchAll()
246
325
  .then(({ resources }) =>
247
326
  resources.map(
@@ -256,31 +335,36 @@ function makeCosmosStore({ prefix }: StorageConfig) {
256
335
  }, { captureStackTrace: false })
257
336
  ),
258
337
  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
338
+ resolvePartitionKeyAndSeed.pipe(Effect.flatMap((pk) =>
339
+ Effect.promise(() =>
340
+ execBatch(
341
+ mutable(ids.map((id) =>
342
+ dropUndefinedT({
343
+ operationType: "Delete" as const,
344
+ id
345
+ // don't use this or we get an error that the request and some item partition key dont match - makese no sense
346
+ // partitionKey: config?.partitionValue({ [idKey]: id } as Encoded)
347
+ })
348
+ )),
349
+ partitionKey ?? pk
350
+ )
270
351
  )
271
- ),
352
+ )),
272
353
  all: Effect
273
- .sync(() => ({
274
- query: `SELECT * FROM ${name}`,
275
- parameters: []
276
- }))
354
+ .all({
355
+ q: Effect.sync(() => ({
356
+ query: `SELECT * FROM ${name}`,
357
+ parameters: []
358
+ })),
359
+ pk: resolvePartitionKeyAndSeed
360
+ })
277
361
  .pipe(
278
- Effect.tap((q) => logQuery(q)),
279
- Effect.flatMap((q) =>
362
+ Effect.tap(({ q }) => logQuery(q)),
363
+ Effect.flatMap(({ pk, q }) =>
280
364
  Effect.promise(() =>
281
365
  container
282
366
  .items
283
- .query<PMCosmos>(q, { partitionKey: mainPartitionKey })
367
+ .query<PMCosmos>(q, { partitionKey: pk })
284
368
  .fetchAll()
285
369
  .then(({ resources }) =>
286
370
  resources.map(
@@ -305,42 +389,45 @@ function makeCosmosStore({ prefix }: StorageConfig) {
305
389
  const filter = f.filter
306
390
  type M = U extends undefined ? Encoded : Pick<Encoded, U>
307
391
  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
- )
392
+ .all({
393
+ q: Effect.sync(() =>
394
+ buildWhereCosmosQuery3(
395
+ idKey,
396
+ filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
397
+ name,
398
+ defaultValues,
399
+ f.select as
400
+ | NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }>
401
+ | undefined,
402
+ f.order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
403
+ skip,
404
+ limit
405
+ )
406
+ ),
407
+ pk: resolvePartitionKey
408
+ })
320
409
  .pipe(
321
- Effect.tap((q) => logQuery(q)),
410
+ Effect.tap(({ q }) => logQuery(q)),
322
411
  Effect
323
- .flatMap((q) =>
412
+ .flatMap(({ pk, q }) =>
324
413
  Effect.promise(() =>
325
414
  f.select
326
415
  ? container
327
416
  .items
328
- .query<M>(q, { partitionKey: mainPartitionKey })
417
+ .query<M>(q, { partitionKey: pk })
329
418
  .fetchAll()
330
419
  .then(({ resources }) =>
331
- resources.map((_) =>
332
- ({
333
- ...pipe(
334
- defaultValues,
335
- Struct.pick(f.select!.filter((_) => typeof _ === "string"))
336
- ),
337
- ...mapReverseId(_ as any)
338
- }) as any
339
- )
420
+ resources.map((_) => ({
421
+ ...pipe(
422
+ defaultValues,
423
+ Struct.pick(f.select!.filter((_) => typeof _ === "string") as never[])
424
+ ),
425
+ ...mapReverseId(_ as any)
426
+ }))
340
427
  )
341
428
  : container
342
429
  .items
343
- .query<{ f: M }>(q, { partitionKey: mainPartitionKey })
430
+ .query<{ f: M }>(q, { partitionKey: pk })
344
431
  .fetchAll()
345
432
  .then(({ resources }) =>
346
433
  resources.map(({ f }) => ({ ...defaultValues, ...mapReverseId(f as any) }) as any)
@@ -355,114 +442,93 @@ function makeCosmosStore({ prefix }: StorageConfig) {
355
442
  )
356
443
  },
357
444
  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
- }
445
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
446
+ Effect
447
+ .promise(() =>
448
+ container
449
+ .item(id, nsPartitionValue(ns, { [idKey]: id } as Encoded))
450
+ .read<Encoded>()
451
+ .then(({ resource }) =>
452
+ Option.fromNullishOr(resource).pipe(
453
+ Option.map((_) => ({ ...defaultValues, ...mapReverseId(_) }))
399
454
  )
400
455
  )
401
- }
402
- )
403
- .pipe(
404
- Effect
405
- .flatMap((x) => {
406
- if (x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409) {
407
- return Effect.fail(new OptimisticConcurrencyException({ type: name, id: e[idKey], code: x.statusCode }))
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
- )
414
- )
415
- }
416
- return Effect.sync(() => ({
417
- ...e,
418
- _etag: x.etag
419
- }))
420
- }),
421
- Effect
422
- .withSpan("Cosmos.set [effect-app/infra/Store]", {
456
+ )
457
+ .pipe(Effect
458
+ .withSpan("Cosmos.find [effect-app/infra/Store]", {
423
459
  attributes: {
424
460
  "repository.container_id": containerId,
425
461
  "repository.model_name": name,
426
- id: e[idKey]
462
+ partitionValue: nsPartitionValue(ns, { [idKey]: id } as Encoded),
463
+ id
427
464
  }
428
- }, { captureStackTrace: false })
429
- ),
465
+ }, { captureStackTrace: false }))
466
+ )),
467
+ set: (e) =>
468
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
469
+ Option
470
+ .match(
471
+ Option
472
+ .fromNullishOr(e._etag),
473
+ {
474
+ onNone: () =>
475
+ Effect.promise(() =>
476
+ container.items.create({
477
+ ...mapId(e),
478
+ _partitionKey: nsPartitionValue(ns, e)
479
+ })
480
+ ),
481
+ onSome: (eTag) =>
482
+ Effect.promise(() =>
483
+ container.item(e[idKey], nsPartitionValue(ns, e)).replace(
484
+ { ...mapId(e), _partitionKey: nsPartitionValue(ns, e) },
485
+ {
486
+ accessCondition: {
487
+ type: "IfMatch",
488
+ condition: eTag
489
+ }
490
+ }
491
+ )
492
+ )
493
+ }
494
+ )
495
+ .pipe(
496
+ Effect
497
+ .flatMap((x) => {
498
+ if (x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409) {
499
+ return Effect.fail(
500
+ new OptimisticConcurrencyException({ type: name, id: e[idKey], code: x.statusCode })
501
+ )
502
+ }
503
+ if (x.statusCode > 299 || x.statusCode < 200) {
504
+ return Effect.die(
505
+ new CosmosDbOperationError(
506
+ "not able to update record: " + x.statusCode
507
+ )
508
+ )
509
+ }
510
+ return Effect.sync(() => ({
511
+ ...e,
512
+ _etag: x.etag
513
+ }))
514
+ }),
515
+ Effect
516
+ .withSpan("Cosmos.set [effect-app/infra/Store]", {
517
+ attributes: {
518
+ "repository.container_id": containerId,
519
+ "repository.model_name": name,
520
+ id: e[idKey]
521
+ }
522
+ }, { captureStackTrace: false })
523
+ )
524
+ )),
430
525
  batchSet,
431
526
  bulkSet
432
527
  }
433
528
 
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
- )
529
+ // Eagerly seed primary namespace on initialization
530
+ yield* seedNamespace("primary")
441
531
 
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
532
  return s
467
533
  })
468
534
  }
package/src/Store/Disk.ts CHANGED
@@ -66,11 +66,10 @@ function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValu
66
66
  }
67
67
 
68
68
  // lock file for cross-process coordination during initialization
69
- const lockFile = file + ".lock"
70
69
 
71
70
  // wrap initialization in file lock to prevent race conditions in multi-worker setups
72
71
  const store = yield* fu.withFileLock(
73
- lockFile,
72
+ file,
74
73
  Effect.gen(function*() {
75
74
  const shouldSeed = !(fs.existsSync(file))
76
75
 
@@ -143,7 +142,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
143
142
  const storesSem = Semaphore.makeUnsafe(1)
144
143
  const primary = yield* makeDiskStoreInt(prefix, idKey, "primary", dir, name, seed, config?.defaultValues)
145
144
  const stores = new Map<string, Store<IdKey, Encoded>>([["primary", primary]])
146
- const ctx = yield* Effect.services<R>()
145
+ const ctx = yield* Effect.context<R>()
147
146
  const getStore = !config?.allowNamespace
148
147
  ? Effect.succeed(primary)
149
148
  : storeId.asEffect().pipe(Effect.flatMap((namespace) => {
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
- import { Array, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, ServiceMap, Struct } from "effect-app"
3
+ import { Array, Context, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, Struct } from "effect-app"
4
4
  import { NonEmptyString255 } from "effect-app/Schema"
5
5
  import { get } from "effect-app/utils"
6
6
  import { InfraLogger } from "../logger.js"
@@ -24,7 +24,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
24
24
  )
25
25
  const n = Struct.pick(i, keys)
26
26
  subKeys.forEach((subKey) => {
27
- n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys))
27
+ n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys as never[]))
28
28
  })
29
29
  return n as M
30
30
  }) as any
@@ -72,9 +72,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
72
72
  }
73
73
 
74
74
  const defaultNs: NonEmptyString255 = NonEmptyString255("primary")
75
- export class storeId
76
- extends ServiceMap.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs })
77
- {}
75
+ export class storeId extends Context.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs }) {}
78
76
 
79
77
  function logQuery(f: FilterArgs<any, any>, defaultValues?: any) {
80
78
  return InfraLogger
@@ -267,7 +265,7 @@ export const makeMemoryStore = () => ({
267
265
  seed,
268
266
  config?.defaultValues
269
267
  )
270
- const ctx = yield* Effect.services<R>()
268
+ const ctx = yield* Effect.context<R>()
271
269
  const stores = new Map([["primary", primary]])
272
270
  const getStore = !config?.allowNamespace
273
271
  ? Effect.succeed(primary)