Skip to content

Commit 16c906a

Browse files
committed
DocumentCollection+Collection: add findOneAndReplace (close #3)
1 parent a8530e7 commit 16c906a

File tree

5 files changed

+185
-0
lines changed

5 files changed

+185
-0
lines changed

.changeset/shaggy-bats-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect-mongodb": patch
3+
---
4+
5+
Add findOneAndReplace function in Collection and DocumentCollection

packages/effect-mongodb/src/Collection.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import type {
1717
Document,
1818
DropCollectionOptions,
1919
DropIndexesOptions,
20+
FindOneAndReplaceOptions,
2021
FindOptions as MongoFindOptions,
2122
IndexDescription,
2223
IndexSpecification,
2324
InsertManyResult,
2425
InsertOneOptions,
2526
InsertOneResult,
27+
ModifyResult as MongoModifyResult,
2628
RenameOptions,
2729
ReplaceOptions,
2830
UpdateFilter,
@@ -32,6 +34,8 @@ import type {
3234
import * as AggregationCursor from "./AggregationCursor.js"
3335
import * as FindCursor from "./FindCursor.js"
3436
import type { Filter } from "./internal/filter.js"
37+
import type { ModifyResult } from "./internal/modify-result.js"
38+
import * as SchemaExt from "./internal/schema.js"
3539
import * as MongoError from "./MongoError.js"
3640

3741
export class Collection<A extends Document, I extends Document = A, R = never> extends Data.TaggedClass("Collection")<{
@@ -256,6 +260,87 @@ export const replaceOne: {
256260
)
257261
)
258262

263+
export const findOneAndReplace: {
264+
<A extends Document, I extends Document>(
265+
filter: Filter<I>,
266+
replacement: A,
267+
options: FindOneAndReplaceOptions & { includeResultMetadata: true }
268+
): <R>(
269+
collection: Collection<A, I, R>
270+
) => Effect.Effect<ModifyResult<A>, MongoError.MongoError | ParseResult.ParseError, R>
271+
<A extends Document, I extends Document>(
272+
filter: Filter<I>,
273+
replacement: A,
274+
options: FindOneAndReplaceOptions & { includeResultMetadata: false }
275+
): <R>(
276+
collection: Collection<A, I, R>
277+
) => Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
278+
<A extends Document, I extends Document>(
279+
filter: Filter<I>,
280+
replacement: A,
281+
options: FindOneAndReplaceOptions
282+
): <R>(
283+
collection: Collection<A, I, R>
284+
) => Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
285+
<A extends Document, I extends Document>(
286+
filter: Filter<I>,
287+
replacement: A
288+
): <R>(
289+
collection: Collection<A, I, R>
290+
) => Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
291+
<A extends Document, I extends Document, R>(
292+
collection: Collection<A, I, R>,
293+
filter: Filter<I>,
294+
replacement: A,
295+
options: FindOneAndReplaceOptions & { includeResultMetadata: true }
296+
): Effect.Effect<ModifyResult<A>, MongoError.MongoError | ParseResult.ParseError, R>
297+
<A extends Document, I extends Document, R>(
298+
collection: Collection<A, I, R>,
299+
filter: Filter<I>,
300+
replacement: A,
301+
options: FindOneAndReplaceOptions & { includeResultMetadata: false }
302+
): Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
303+
<A extends Document, I extends Document, R>(
304+
collection: Collection<A, I, R>,
305+
filter: Filter<I>,
306+
replacement: A,
307+
options: FindOneAndReplaceOptions
308+
): Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
309+
<A extends Document, I extends Document, R>(
310+
collection: Collection<A, I, R>,
311+
filter: Filter<I>,
312+
replacement: A
313+
): Effect.Effect<O.Option<A>, MongoError.MongoError | ParseResult.ParseError, R>
314+
} = F.dual(
315+
(args) => isCollection(args[0]),
316+
<A extends Document, I extends Document, R>(
317+
collection: Collection<A, I, R>,
318+
filter: Filter<I>,
319+
replacement: A,
320+
options?: FindOneAndReplaceOptions
321+
): Effect.Effect<O.Option<A> | ModifyResult<A>, MongoError.MongoError | ParseResult.ParseError, R> =>
322+
F.pipe(
323+
// TODO: extract function in Collection
324+
Schema.encode(collection.schema)(replacement),
325+
Effect.flatMap((replacement) =>
326+
Effect.promise(() => collection.collection.findOneAndReplace(filter, replacement, options ?? {}))
327+
),
328+
Effect.flatMap((value) =>
329+
Effect.gen(function*(_) {
330+
if (options?.includeResultMetadata && !!value) {
331+
const result = value as unknown as MongoModifyResult<I>
332+
const maybeValue = yield* _(SchemaExt.decodeNullableDocument(collection.schema, result.value))
333+
return { ...result, value: maybeValue }
334+
}
335+
return yield* _(SchemaExt.decodeNullableDocument(collection.schema, value))
336+
})
337+
),
338+
Effect.catchAllDefect(
339+
MongoError.mongoErrorDie<O.Option<A> | ModifyResult<A>>("findOneAndReplace error")
340+
)
341+
)
342+
)
343+
259344
export const rename: {
260345
(
261346
newName: string,

packages/effect-mongodb/src/DocumentCollection.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import type {
1717
DropCollectionOptions,
1818
DropIndexesOptions,
1919
Filter,
20+
FindOneAndReplaceOptions,
2021
FindOptions,
2122
IndexDescription,
2223
IndexSpecification,
2324
InsertManyResult,
2425
InsertOneOptions,
2526
InsertOneResult,
27+
ModifyResult as MongoModifyResult,
2628
OptionalUnlessRequiredId,
2729
RenameOptions,
2830
ReplaceOptions,
@@ -34,6 +36,7 @@ import type {
3436
import * as Collection from "./Collection.js"
3537
import * as DocumentAggregationCursor from "./DocumentAggregationCursor.js"
3638
import * as DocumentFindCursor from "./DocumentFindCursor.js"
39+
import type { ModifyResult } from "./internal/modify-result.js"
3740
import * as MongoError from "./MongoError.js"
3841

3942
export class DocumentCollection extends Data.TaggedClass("DocumentCollection")<{
@@ -237,6 +240,80 @@ export const replaceOne: {
237240
)
238241
)
239242

243+
export const findOneAndReplace: {
244+
(
245+
filter: Filter<Document>,
246+
replacement: WithoutId<Document>,
247+
options: FindOneAndReplaceOptions & { includeResultMetadata: true }
248+
): (
249+
collection: DocumentCollection
250+
) => Effect.Effect<ModifyResult<Document>, MongoError.MongoError>
251+
(
252+
filter: Filter<Document>,
253+
replacement: WithoutId<Document>,
254+
options: FindOneAndReplaceOptions & { includeResultMetadata: false }
255+
): (
256+
collection: DocumentCollection
257+
) => Effect.Effect<O.Option<Document>, MongoError.MongoError>
258+
(
259+
filter: Filter<Document>,
260+
replacement: WithoutId<Document>,
261+
options: FindOneAndReplaceOptions
262+
): (
263+
collection: DocumentCollection
264+
) => Effect.Effect<O.Option<Document>, MongoError.MongoError>
265+
(
266+
filter: Filter<Document>,
267+
replacement: WithoutId<Document>
268+
): (
269+
collection: DocumentCollection
270+
) => Effect.Effect<O.Option<Document>, MongoError.MongoError>
271+
(
272+
collection: DocumentCollection,
273+
filter: Filter<Document>,
274+
replacement: WithoutId<Document>,
275+
options: FindOneAndReplaceOptions & { includeResultMetadata: true }
276+
): Effect.Effect<ModifyResult<Document>, MongoError.MongoError>
277+
(
278+
collection: DocumentCollection,
279+
filter: Filter<Document>,
280+
replacement: WithoutId<Document>,
281+
options: FindOneAndReplaceOptions & { includeResultMetadata: false }
282+
): Effect.Effect<O.Option<Document>, MongoError.MongoError>
283+
(
284+
collection: DocumentCollection,
285+
filter: Filter<Document>,
286+
replacement: WithoutId<Document>,
287+
options: FindOneAndReplaceOptions
288+
): Effect.Effect<O.Option<Document>, MongoError.MongoError>
289+
(
290+
collection: DocumentCollection,
291+
filter: Filter<Document>,
292+
replacement: WithoutId<Document>
293+
): Effect.Effect<O.Option<Document>, MongoError.MongoError>
294+
} = F.dual(
295+
(args) => isDocumentCollection(args[0]),
296+
(
297+
collection: DocumentCollection,
298+
filter: Filter<Document>,
299+
replacement: WithoutId<Document>,
300+
options?: FindOneAndReplaceOptions
301+
): Effect.Effect<O.Option<Document> | ModifyResult<Document>, MongoError.MongoError> =>
302+
F.pipe(
303+
Effect.promise(() => collection.collection.findOneAndReplace(filter, replacement, options ?? {})),
304+
Effect.map((value) => {
305+
if (options?.includeResultMetadata && !!value) {
306+
const result = value as unknown as MongoModifyResult<Document>
307+
return { ...result, value: O.fromNullable(result.value) }
308+
}
309+
return O.fromNullable(value)
310+
}),
311+
Effect.catchAllDefect(
312+
MongoError.mongoErrorDie<O.Option<Document> | ModifyResult<Document>>("findOneAndReplace error")
313+
)
314+
)
315+
)
316+
240317
export const rename: {
241318
(
242319
newName: string,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type * as O from "effect/Option"
2+
import type { ModifyResult as MongoModifyResult } from "mongodb"
3+
4+
export type ModifyResult<TSchema> = Omit<MongoModifyResult<TSchema>, "value"> & {
5+
value: O.Option<TSchema>
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Effect from "effect/Effect"
2+
import * as O from "effect/Option"
3+
import * as Schema from "effect/Schema"
4+
import type { Document } from "mongodb"
5+
6+
// TODO: there is probably a Schema to do this decode or using typeclass package for traverse
7+
export const decodeNullableDocument = <A, I, R>(schema: Schema.Schema<A, I, R>, value: Document | null) =>
8+
Effect.gen(function*(_) {
9+
if (value === null) return O.none()
10+
const decoded = yield* _(Schema.decodeUnknown(schema)(value))
11+
return O.some(decoded)
12+
})

0 commit comments

Comments
 (0)