TypeScriptでサブドキュメントを処理する

TypeScriptではサブドキュメントは扱いにくいものです。デフォルトでは、Mongooseはドキュメントインターフェースのオブジェクトプロパティをサブドキュメントではなく、ネストされたプロパティとして扱います。

// Setup
import { Schema, Types, model, Model } from 'mongoose';

// Subdocument definition
interface Names {
  _id: Types.ObjectId;
  firstName: string;
}

// Document definition
interface User {
  names: Names;
}

// Models and schemas
type UserModelType = Model<User>;
const userSchema = new Schema<User, UserModelType>({
  names: new Schema<Names>({ firstName: String })
});
const UserModel = model<User, UserModelType>('User', userSchema);

// Create a new document:
const doc = new UserModel({ names: { _id: '0'.repeat(24), firstName: 'foo' } });

// "Property 'ownerDocument' does not exist on type 'Names'."
// Means that `doc.names` is not a subdocument!
doc.names.ownerDocument();

Mongooseは、ハイドレートされたドキュメントの型をオーバーライドするメカニズムを提供します。別のTHydratedDocumentTypeを定義し、mongoose.Model<>の5番目のジェネリックパラメータとして渡します。THydratedDocumentTypeは、Mongooseが「ハイドレートされたドキュメント」に対して使用する型、つまりawait UserModel.findOne()UserModel.hydrate()、およびnew UserModel()が返すものを制御します。

// Define property overrides for hydrated documents
type THydratedUserDocument = {
  names?: mongoose.Types.Subdocument<Names>
}
type UserModelType = mongoose.Model<User, {}, {}, {}, THydratedUserDocument>;

const userSchema = new mongoose.Schema<User, UserModelType>({
  names: new mongoose.Schema<Names>({ firstName: String })
});
const UserModel = mongoose.model<User, UserModelType>('User', userSchema);

const doc = new UserModel({ names: { _id: '0'.repeat(24), firstName: 'foo' } });
doc.names!.ownerDocument(); // Works, `names` is a subdocument!

サブドキュメント配列

TMethodsAndOverridesを使用してサブドキュメント配列を適切に型付けするように配列をオーバーライドすることもできます

// Subdocument definition
interface Names {
  _id: Types.ObjectId;
  firstName: string;
}
// Document definition
interface User {
  names: Names[];
}

// TMethodsAndOverrides
type THydratedUserDocument = {
  names?: Types.DocumentArray<Names>
}
type UserModelType = Model<User, {}, {}, {}, THydratedUserDocument>;

// Create model
const UserModel = model<User, UserModelType>('User', new Schema<User, UserModelType>({
  names: [new Schema<Names>({ firstName: String })]
}));

const doc = new UserModel({});
doc.names[0].ownerDocument(); // Works!