Skip to content

Commit aeff950

Browse files
committed
fix: simplify mongo cursor typings across meteor and webui
1 parent c878aeb commit aeff950

12 files changed

Lines changed: 159 additions & 205 deletions

File tree

meteor/__mocks__/mongo.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
22
import { InMemoryMongoCollection } from '@sofie-automation/corelib/dist/memoryCollection'
3-
import { MongoReadOnlyCollection } from '@sofie-automation/meteor-lib/dist/collections/lib'
43
import { AsyncOnlyMongoCollection, AsyncOnlyReadOnlyMongoCollection } from '../server/collections/collection'
54
import { Collections } from '../server/collections/lib'
65

@@ -34,7 +33,7 @@ export namespace MongoMock {
3433

3534
/** The backing in-memory collection for a wrapped (mock) collection. */
3635
export function getInnerMockCollection<T extends { _id: ProtectedString<any> }>(
37-
collection: MongoReadOnlyCollection<T> | AsyncOnlyReadOnlyMongoCollection<T>
36+
collection: AsyncOnlyReadOnlyMongoCollection<T>
3837
): InMemoryMongoCollection<T> {
3938
return (collection as any).mockCollection
4039
}

meteor/server/api/deviceTriggers/StudioObserver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { RundownContentObserver } from './RundownContentObserver'
2121
import { RundownsObserver } from './RundownsObserver'
2222
import { RundownPlaylists, Rundowns, ShowStyleBases } from '../../collections'
2323
import { PromiseDebounce } from '../../publications/lib/PromiseDebounce'
24-
import { MinimalMongoCursor } from '../../collections/implementations/asyncCollection'
2524
import { PieceInstancesObserver } from './PieceInstancesObserver'
25+
import { MinimalMongoCursor } from '../../collections/collection'
2626

2727
type RundownContentChangeHandler = (showStyleBaseId: ShowStyleBaseId, cache: ContentCache) => () => void
2828
type PieceInstancesChangeHandler = (showStyleBaseId: ShowStyleBaseId, cache: PieceInstancesContentCache) => () => void

meteor/server/collections/changeStream/changeStreamCursor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '@sofie-automation/corelib/dist/mongo'
1010
import { PromisifyCallbacks } from '@sofie-automation/shared-lib/dist/lib/types'
1111
import { observeChangesViaChangeStream, observeViaChangeStream, ObserveMultiplexerDeps } from './observeMultiplexer'
12-
import type { MinimalMongoCursor } from '../implementations/asyncCollection'
12+
import type { MinimalMongoCursor } from '../collection'
1313

1414
export interface ChangeStreamCursorConfig<TDoc extends { _id: ProtectedString<any> }> {
1515
collectionName: string
@@ -27,8 +27,6 @@ export interface ChangeStreamCursorConfig<TDoc extends { _id: ProtectedString<an
2727
* A lazy "watcher" returned by `findWithCursor`. It exposes the async cursor methods the codebase uses
2828
* (`fetchAsync`/`countAsync`/`observeChangesAsync`/`observeAsync`) plus a plain `collectionName`.
2929
*
30-
* Unlike a Meteor cursor it has NO `_publishCursor`: publications are wired up by `meteorPublish`
31-
* (`driveSubscriptionFromCursor`).
3230
* Observing goes through the shared change-stream multiplexer, so identical queries are de-duplicated.
3331
*/
3432
export class ChangeStreamCursor<TDoc extends { _id: ProtectedString<any> }> implements MinimalMongoCursor<TDoc> {

meteor/server/collections/collection.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ import { createMockCollection } from './implementations/mock'
1717
import { WrappedAsyncMongoCollection } from './implementations/asyncCollection'
1818
import { WrappedReadOnlyMongoCollection } from './implementations/readonlyWrapper'
1919
import { isInMockMode } from './mongoConnection'
20-
import { FieldNames, IndexSpecifier, UpdateOptions } from '@sofie-automation/meteor-lib/dist/collections/lib'
21-
import { MinimalMongoCursor } from './implementations/asyncCollection'
20+
import {
21+
FieldNames,
22+
IndexSpecifier,
23+
MongoLiveQueryHandle,
24+
UpdateOptions,
25+
} from '@sofie-automation/meteor-lib/dist/collections/lib'
2226
import { UserPermissions } from '@sofie-automation/meteor-lib/dist/userPermissions'
2327

2428
export interface CustomMongoAllowRules<DBInterface> {
@@ -86,6 +90,42 @@ function createWrappedCollection<DBInterface extends { _id: ProtectedString<any>
8690
}
8791
}
8892

93+
/**
94+
* A minimal mongo cursor, with only the async methods used by the codebase.
95+
*/
96+
export interface MinimalMongoCursor<T extends { _id: ProtectedString<any> }> {
97+
readonly collectionName: string | null
98+
99+
/**
100+
* Returns the number of documents that match a query.
101+
* @param applySkipLimit If set to `false`, the value returned will reflect the total number of matching documents, ignoring any value supplied for limit. (Default: true)
102+
*/
103+
countAsync(applySkipLimit?: boolean): Promise<number>
104+
105+
/**
106+
* Return all matching documents as an Array.
107+
*/
108+
fetchAsync(): Promise<Array<T>>
109+
110+
// Future: this might be nice to have
111+
// [Symbol.asyncIterator](): AsyncIterator<T, undefined, never>
112+
113+
/**
114+
* Watch a query. Receive callbacks as the result set changes.
115+
* @param callbacks Functions to call to deliver the result set as it changes
116+
*/
117+
observeAsync(callbacks: ObserveCallbacks<T>): Promise<MongoLiveQueryHandle>
118+
/**
119+
* Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks.
120+
* @param callbacks Functions to call to deliver the result set as it changes
121+
* @param options { nonMutatingCallbacks: boolean }
122+
*/
123+
observeChangesAsync(
124+
callbacks: ObserveChangesCallbacks<T>,
125+
options?: { nonMutatingCallbacks?: boolean | undefined }
126+
): Promise<MongoLiveQueryHandle>
127+
}
128+
89129
/**
90130
* A minimal Async only wrapping around the base Mongo.Collection type
91131
*/

meteor/server/collections/implementations/asyncCollection.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@ import {
88
} from '@sofie-automation/corelib/dist/mongo'
99
import { ProtectedString, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
1010
import { Meteor } from 'meteor/meteor'
11-
import {
12-
UpdateOptions,
13-
IndexSpecifier,
14-
MongoCursor,
15-
FindOptions,
16-
} from '@sofie-automation/meteor-lib/dist/collections/lib'
11+
import { UpdateOptions, IndexSpecifier, FindOptions } from '@sofie-automation/meteor-lib/dist/collections/lib'
1712
import type {
1813
Collection as RawCollection,
1914
CreateIndexesOptions,
@@ -25,7 +20,7 @@ import { MongoFieldSpecifier } from '@sofie-automation/corelib/dist/mongo'
2520
import { profiler } from '../../api/profiler'
2621
import { logger } from '../../logging'
2722
import { PromisifyCallbacks } from '@sofie-automation/shared-lib/dist/lib/types'
28-
import { AsyncOnlyMongoCollection } from '../collection'
23+
import { AsyncOnlyMongoCollection, MinimalMongoCursor } from '../collection'
2924
import { getMongoDb } from '../mongoConnection'
3025
import {
3126
observeChangesViaChangeStream,
@@ -35,15 +30,6 @@ import {
3530
import { ChangeStreamCursor } from '../changeStream/changeStreamCursor'
3631
import { subscribeToCollectionChangeFeed } from '../changeStream/collectionChangeFeed'
3732

38-
/**
39-
* A stripped down version of a cursor, with only the async methods used by the codebase, plus the name of
40-
* the collection it came from (used by the publish layer to address DDP messages).
41-
*/
42-
export type MinimalMongoCursor<T extends { _id: ProtectedString<any> }> = Pick<
43-
MongoCursor<T>,
44-
'fetchAsync' | 'observeChangesAsync' | 'observeAsync' | 'countAsync'
45-
> & { readonly collectionName: string | null }
46-
4733
/**
4834
* Translate a meteor-lib {@link FindOptions} into the options the native `mongodb` driver accepts.
4935
* This is an explicit allow-list: only options known to be understood by the driver are forwarded.

meteor/server/collections/implementations/mock.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import type { CreateIndexesOptions, IndexDescriptionInfo } from 'mongodb'
1313
import { InMemoryMongoCollection } from '@sofie-automation/corelib/dist/memoryCollection'
1414
import { PromisifyCallbacks } from '@sofie-automation/shared-lib/dist/lib/types'
1515
import { UpdateOptions, IndexSpecifier } from '@sofie-automation/meteor-lib/dist/collections/lib'
16-
import { AsyncOnlyMongoCollection } from '../collection'
17-
import { MinimalMongoCursor } from './asyncCollection'
16+
import { AsyncOnlyMongoCollection, MinimalMongoCursor } from '../collection'
1817

1918
/**
2019
* {@link WrappedMockCollection} only ever runs under jest, where `Meteor` is the mock that provides

meteor/server/collections/implementations/readonlyWrapper.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
22
import { Meteor } from 'meteor/meteor'
33
import type { IndexDescriptionInfo } from 'mongodb'
4-
import type { AsyncOnlyMongoCollection, AsyncOnlyReadOnlyMongoCollection } from '../collection'
5-
import type { MinimalMongoCursor } from './asyncCollection'
4+
import type { AsyncOnlyMongoCollection, AsyncOnlyReadOnlyMongoCollection, MinimalMongoCursor } from '../collection'
65

76
export class WrappedReadOnlyMongoCollection<
87
DBInterface extends { _id: ProtectedString<any> },

meteor/server/publications/lib/lib.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AllPubSubCollections, AllPubSubTypes } from '@sofie-automation/meteor-l
33
import { extractFunctionSignature } from '../../lib'
44
import { protectStringObject, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
55
import { MetricsGauge } from '@sofie-automation/corelib/dist/prometheus'
6-
import { MinimalMongoCursor } from '../../collections/implementations/asyncCollection'
6+
import { MinimalMongoCursor } from '../../collections/collection'
77
import { ChangeStreamCursor } from '../../collections/changeStream/changeStreamCursor'
88

99
export const MeteorPublicationSignatures: { [key: string]: string[] } = {}

meteor/server/publications/lib/observerChain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Simplify } from 'type-fest'
44
import { assertNever } from '@sofie-automation/corelib/dist/lib'
55
import { logger } from '../../logging'
66
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
7-
import { MinimalMongoCursor } from '../../collections/implementations/asyncCollection'
7+
import { MinimalMongoCursor } from '../../collections/collection'
88

99
/**
1010
* https://stackoverflow.com/a/66011942

packages/meteor-lib/src/collections/lib.ts

Lines changed: 1 addition & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,4 @@
1-
import type { Collection as RawCollection } from 'mongodb'
2-
import {
3-
MongoFieldSpecifier,
4-
MongoModifier,
5-
MongoQuery,
6-
SortSpecifier,
7-
ObserveCallbacks,
8-
ObserveChangesCallbacks,
9-
} from '@sofie-automation/corelib/dist/mongo'
10-
import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
11-
12-
export interface MongoReadOnlyCollection<DBInterface extends { _id: ProtectedString<any> }> {
13-
/**
14-
* Find the documents in a collection that match the selector.
15-
* @param selector A query describing the documents to find
16-
*/
17-
find(
18-
selector?: MongoQuery<DBInterface> | DBInterface['_id'],
19-
options?: FindOptions<DBInterface>
20-
): MongoCursor<DBInterface>
21-
22-
/**
23-
* Finds the first document that matches the selector, as ordered by sort and skip options. Returns `undefined` if no matching document is found.
24-
* @param selector A query describing the documents to find
25-
*/
26-
findOne(
27-
selector?: MongoQuery<DBInterface> | DBInterface['_id'],
28-
options?: FindOneOptions<DBInterface>
29-
): DBInterface | undefined
30-
}
31-
/**
32-
* A minimal MongoCollection type based on the Meteor Mongo.Collection type, but with our improved _id type safety.
33-
* Note: when updating method signatures, make sure to update the implementions as new properties may not be fed through without additional work
34-
*/
35-
export interface MongoCollection<
36-
DBInterface extends { _id: ProtectedString<any> },
37-
> extends MongoReadOnlyCollection<DBInterface> {
38-
/**
39-
* Insert a document in the collection. Returns its unique _id.
40-
* @param doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you.
41-
*/
42-
insert(doc: DBInterface): DBInterface['_id']
43-
44-
/**
45-
* Returns the [`Collection`](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html) object corresponding to this collection from the
46-
* [npm `mongodb` driver module](https://www.npmjs.com/package/mongodb) which is wrapped by `Mongo.Collection`.
47-
*/
48-
rawCollection(): RawCollection<DBInterface>
49-
50-
/**
51-
* Remove documents from the collection
52-
* @param selector Specifies which documents to remove
53-
*/
54-
remove(selector: MongoQuery<DBInterface> | DBInterface['_id']): number
55-
56-
/**
57-
* Modify one or more documents in the collection. Returns the number of matched documents.
58-
* @param selector Specifies which documents to modify
59-
* @param modifier Specifies how to modify the documents
60-
*/
61-
update(
62-
selector: DBInterface['_id'] | { _id: DBInterface['_id'] },
63-
modifier: MongoModifier<DBInterface>,
64-
options?: UpdateOptions
65-
): number
66-
update(
67-
selector: MongoQuery<DBInterface>,
68-
modifier: MongoModifier<DBInterface>,
69-
// Require { multi } to be set when selecting multiple documents to be updated, otherwise only the first found document will be updated
70-
options: UpdateOptions & Required<Pick<UpdateOptions, 'multi'>>
71-
): number
72-
73-
/**
74-
* Modify one or more documents in the collection, or insert one if no matching documents were found. Returns an object with keys `numberAffected` (the number of documents modified) and
75-
* `insertedId` (the unique _id of the document that was inserted, if any).
76-
* @param selector Specifies which documents to modify
77-
* @param modifier Specifies how to modify the documents
78-
*/
79-
upsert(
80-
selector: MongoQuery<DBInterface> | DBInterface['_id'],
81-
modifier: MongoModifier<DBInterface>,
82-
options?: UpsertOptions
83-
): {
84-
numberAffected?: number
85-
insertedId?: DBInterface['_id']
86-
}
87-
}
1+
import { MongoFieldSpecifier, SortSpecifier } from '@sofie-automation/corelib/dist/mongo'
882

893
export interface UpdateOptions {
904
/** True to modify all matching documents; false to only modify one of the matching documents (the default). */
@@ -110,88 +24,6 @@ export interface MongoLiveQueryHandle {
11024
stop(): void
11125
}
11226

113-
// Note: This is a subset of the Meteor Mongo.Cursor type
114-
export interface MongoCursor<DBInterface extends { _id: ProtectedString<any> }> {
115-
/**
116-
* Returns the number of documents that match a query.
117-
* @param applySkipLimit If set to `false`, the value returned will reflect the total number of matching documents, ignoring any value supplied for limit. (Default: true)
118-
*/
119-
count(applySkipLimit?: boolean): number
120-
/**
121-
* Returns the number of documents that match a query.
122-
* @param applySkipLimit If set to `false`, the value returned will reflect the total number of matching documents, ignoring any value supplied for limit. (Default: true)
123-
*/
124-
countAsync(applySkipLimit?: boolean): Promise<number>
125-
/**
126-
* Return all matching documents as an Array.
127-
*/
128-
fetch(): Array<DBInterface>
129-
/**
130-
* Return all matching documents as an Array.
131-
*/
132-
fetchAsync(): Promise<Array<DBInterface>>
133-
/**
134-
* Call `callback` once for each matching document, sequentially and
135-
* synchronously.
136-
* @param callback Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself.
137-
* @param thisArg An object which will be the value of `this` inside `callback`.
138-
*/
139-
forEach(callback: (doc: DBInterface, index: number, cursor: MongoCursor<DBInterface>) => void, thisArg?: any): void
140-
/**
141-
* Call `callback` once for each matching document, sequentially and
142-
* synchronously.
143-
* @param callback Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself.
144-
* @param thisArg An object which will be the value of `this` inside `callback`.
145-
*/
146-
forEachAsync(
147-
callback: (doc: DBInterface, index: number, cursor: MongoCursor<DBInterface>) => void,
148-
thisArg?: any
149-
): Promise<void>
150-
/**
151-
* Map callback over all matching documents. Returns an Array.
152-
* @param callback Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself.
153-
* @param thisArg An object which will be the value of `this` inside `callback`.
154-
*/
155-
map<M>(callback: (doc: DBInterface, index: number, cursor: MongoCursor<DBInterface>) => M, thisArg?: any): Array<M>
156-
/**
157-
* Map callback over all matching documents. Returns an Array.
158-
* @param callback Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself.
159-
* @param thisArg An object which will be the value of `this` inside `callback`.
160-
*/
161-
mapAsync<M>(
162-
callback: (doc: DBInterface, index: number, cursor: MongoCursor<DBInterface>) => M,
163-
thisArg?: any
164-
): Promise<Array<M>>
165-
166-
[Symbol.iterator](): Iterator<DBInterface, never, never>
167-
[Symbol.asyncIterator](): AsyncIterator<DBInterface, never, never>
168-
169-
/**
170-
* Watch a query. Receive callbacks as the result set changes.
171-
* @param callbacks Functions to call to deliver the result set as it changes
172-
*/
173-
observe(callbacks: ObserveCallbacks<DBInterface>): MongoLiveQueryHandle
174-
/**
175-
* Watch a query. Receive callbacks as the result set changes.
176-
* @param callbacks Functions to call to deliver the result set as it changes
177-
*/
178-
observeAsync(callbacks: ObserveCallbacks<DBInterface>): Promise<MongoLiveQueryHandle>
179-
/**
180-
* Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks.
181-
* @param callbacks Functions to call to deliver the result set as it changes
182-
*/
183-
observeChanges(callbacks: ObserveChangesCallbacks<DBInterface>): MongoLiveQueryHandle
184-
/**
185-
* Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks.
186-
* @param callbacks Functions to call to deliver the result set as it changes
187-
* @param options { nonMutatingCallbacks: boolean }
188-
*/
189-
observeChangesAsync(
190-
callbacks: ObserveChangesCallbacks<DBInterface>,
191-
options?: { nonMutatingCallbacks?: boolean | undefined }
192-
): Promise<MongoLiveQueryHandle>
193-
}
194-
19527
export interface FindOneOptions<TRawDoc> {
19628
/** Sort order (default: natural order) */
19729
sort?: SortSpecifier<TRawDoc>

0 commit comments

Comments
 (0)