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