{"version":3,"sources":["../src/models/query-builder.ts","../src/models/createModel.ts"],"sourcesContent":["import type { SchemaDefinition } from \"../schemas/defineSchema\";\nimport type { MonkoClient } from \"../connections/createConnection\";\nimport type { Filter, WithId, Collection, Document } from \"mongodb\";\nimport { ObjectId } from \"mongodb\";\nimport type {\n  PopulateOptions,\n  QueryBuilder,\n  SingleQueryBuilder,\n  Populate,\n  Prettify,\n  JSONSerialized,\n} from \"./types\";\nimport type { ObjectIdField } from \"../schemas/fields/field-types/objectId\";\n\n// Internal populate info\ninterface PopulateInfo {\n  fields: string[];\n  options: PopulateOptions;\n}\n\n// Global registry for schemas to help with populate\nconst schemaRegistry = new Map<string, SchemaDefinition>();\n\nexport function registerSchema(schema: SchemaDefinition) {\n  schemaRegistry.set(schema.name, schema);\n}\n\nabstract class QueryBuilderBase<Doc extends Document, TReturn>\n  implements PromiseLike<TReturn>\n{\n  protected populates: PopulateInfo[] = [];\n\n  constructor(\n    protected collection: Collection<Doc>,\n    protected schema: SchemaDefinition,\n    protected monkoClient: MonkoClient,\n    protected filter: Filter<Doc>,\n    protected toJSONFunc: (doc: WithId<Doc>) => Prettify<JSONSerialized<WithId<Doc>>>,\n  ) {}\n\n  protected _populate(field: string, options: PopulateOptions = {}) {\n    const fieldArray = [field];\n\n    const totalFields =\n      this.populates.reduce((acc, p) => acc + p.fields.length, 0) +\n      fieldArray.length;\n    const defaultStrategy = totalFields >= 2 ? \"aggregation\" : \"multiple\";\n\n    this.populates.push({\n      fields: fieldArray,\n      options: { strategy: defaultStrategy, ...options },\n    });\n\n    return this;\n  }\n\n  protected abstract executeQuery(): Promise<TReturn>;\n\n  then<TResult1 = TReturn, TResult2 = never>(\n    onfulfilled?:\n      | ((value: TReturn) => TResult1 | PromiseLike<TResult1>)\n      | null,\n    onrejected?:\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n      | null,\n  ): Promise<TResult1 | TResult2> {\n    return this.executeQuery().then(onfulfilled, onrejected);\n  }\n}\n\nexport class QueryBuilderImpl<Doc extends Document>\n  extends QueryBuilderBase<Doc, WithId<Doc>[]>\n  implements QueryBuilder<Doc>\n{\n  populate<T, K extends keyof Doc>(\n    field: K,\n    options: PopulateOptions = {},\n  ): QueryBuilder<Prettify<Populate<Doc, K, T>>> {\n    this._populate(field as string, options);\n    return this as unknown as QueryBuilder<Prettify<Populate<Doc, K, T>>>;\n  }\n\n  async toJSON() {\n    const docs = await this.executeQuery();\n    return docs.map(this.toJSONFunc);\n  }\n\n  protected async executeQuery(): Promise<WithId<Doc>[]> {\n    const docs = await this.collection.find(this.filter || {}).toArray();\n\n    if (!docs || (Array.isArray(docs) && docs.length === 0)) {\n      return docs as WithId<Doc>[];\n    }\n\n    // Process populates\n    const documentsToProcess = Array.isArray(docs) ? docs : [docs];\n\n    for (const populateInfo of this.populates) {\n      // For now, we'll implement only the multiple queries strategy\n      if (\n        populateInfo.options.strategy === \"multiple\" ||\n        populateInfo.options.strategy === undefined\n      ) {\n        await executePopulateWithMultipleQueries(\n          documentsToProcess as WithId<Doc>[],\n          populateInfo,\n          this.schema,\n          this.monkoClient,\n        );\n      }\n      // TODO: Add aggregation strategy implementation\n    }\n\n    return docs as WithId<Doc>[];\n  }\n}\n\nexport class SingleQueryBuilderImpl<Doc extends Document>\n  extends QueryBuilderBase<Doc, WithId<Doc> | null>\n  implements SingleQueryBuilder<Doc>\n{\n  populate<T, K extends keyof Doc>(\n    field: K,\n    options: PopulateOptions = {},\n  ): SingleQueryBuilder<Prettify<Populate<Doc, K, T>>> {\n    this._populate(field as string, options);\n    return this as unknown as SingleQueryBuilder<Prettify<Populate<Doc, K, T>>>;\n  }\n\n  async toJSON() {\n    const doc = await this.executeQuery();\n    if (!doc) return null;\n    return this.toJSONFunc(doc);\n  }\n\n  protected async executeQuery(): Promise<WithId<Doc> | null> {\n    const doc = await this.collection.findOne(this.filter);\n\n    if (!doc) {\n      return null;\n    }\n\n    // Process populates\n    const documentsToProcess = [doc];\n\n    for (const populateInfo of this.populates) {\n      // For now, we'll implement only the multiple queries strategy\n      if (\n        populateInfo.options.strategy === \"multiple\" ||\n        populateInfo.options.strategy === undefined\n      ) {\n        await executePopulateWithMultipleQueries(\n          documentsToProcess as WithId<Doc>[],\n          populateInfo,\n          this.schema,\n          this.monkoClient,\n        );\n      }\n      // TODO: Add aggregation strategy implementation\n    }\n\n    return doc as WithId<Doc> | null;\n  }\n}\n\nasync function executePopulateWithMultipleQueries<Doc>(\n  documents: WithId<Doc>[],\n  populateInfo: PopulateInfo,\n  schema: SchemaDefinition,\n  monkoClient: MonkoClient\n) {\n  for (const fieldName of populateInfo.fields) {\n    // Find the field definition in the schema\n    const fieldDef = schema.fields[fieldName];\n    \n    if (!fieldDef || fieldDef.type !== 'objectId') {\n      console.warn(`Field ${fieldName} is not an ObjectId field or doesn't exist in schema`);\n      continue;\n    }\n\n    // Get the ref from the field definition\n    const ref = (fieldDef as ObjectIdField).ref;\n    if (!ref) {\n      console.warn(`Field ${fieldName} doesn't have a ref property`);\n      continue;\n    }\n\n    // Get the referenced schema to find the correct collection\n    const referencedSchema = schemaRegistry.get(ref);\n    if (!referencedSchema) {\n      console.warn(`Referenced schema ${ref} not found in registry. Make sure to register all schemas.`);\n      continue;\n    }\n\n    // Collect all the IDs to populate\n    const idsToPopulate = new Set<string>();\n    \n    documents.forEach(doc => {\n      const fieldValue = (doc as Record<string, unknown>)[fieldName];\n      if (fieldValue) {\n        if (Array.isArray(fieldValue)) {\n          fieldValue.forEach(id => {\n            if (id) idsToPopulate.add(id.toString());\n          });\n        } else {\n          idsToPopulate.add(fieldValue.toString());\n        }\n      }\n    });\n\n    if (idsToPopulate.size === 0) continue;\n\n    // Convert string IDs back to ObjectIds for the query\n    const objectIds: ObjectId[] = [];\n    const stringIds: string[] = [];\n    \n    Array.from(idsToPopulate).forEach(id => {\n      try {\n        objectIds.push(new ObjectId(id));\n      } catch {\n        stringIds.push(id); // In case it's not a valid ObjectId, keep as string\n      }\n    });\n\n    // Get the referenced collection\n    const refCollection = monkoClient.client\n      .db(referencedSchema.db)\n      .collection(referencedSchema.collection);\n    \n    // Create query filter that handles both ObjectId and string IDs\n    const queryFilter: Record<string, unknown> = {};\n    if (objectIds.length > 0 && stringIds.length > 0) {\n      queryFilter._id = { $in: [...objectIds, ...stringIds] };\n    } else if (objectIds.length > 0) {\n      queryFilter._id = { $in: objectIds };\n    } else if (stringIds.length > 0) {\n      queryFilter._id = { $in: stringIds };\n    }\n    \n    // Fetch the referenced documents\n    const referencedDocs = await refCollection.find(queryFilter).toArray();\n\n    // Create a map for quick lookup\n    const referencedMap = new Map();\n    referencedDocs.forEach(doc => {\n      referencedMap.set(doc._id.toString(), doc);\n    });\n\n    // Replace the IDs with the actual documents\n    documents.forEach(doc => {\n      const docRecord = doc as Record<string, unknown>;\n      const fieldValue = docRecord[fieldName];\n      \n      if (fieldValue) {\n        if (Array.isArray(fieldValue)) {\n          docRecord[fieldName] = fieldValue.map(id => {\n            const refDoc = referencedMap.get(id.toString());\n            return refDoc || id; // Keep original ID if not found\n          });\n        } else {\n          const refDoc = referencedMap.get(fieldValue.toString());\n          docRecord[fieldName] = refDoc || fieldValue; // Keep original ID if not found\n        }\n      }\n    });\n  }\n} ","import type { SchemaDefinition } from \"../schemas/defineSchema\";\nimport type { MonkoClient } from \"../connections/createConnection\";\nimport type { Filter, Document, UpdateFilter, WithId } from \"mongodb\";\nimport type { Model, QueryBuilder, SingleQueryBuilder } from \"./types\";\n\n// Type for document creation - excludes _id which is auto-generated by MongoDB\ntype CreateDocument<Doc> = Omit<Doc, \"_id\">;\n\nimport {\n  QueryBuilderImpl,\n  SingleQueryBuilderImpl,\n  registerSchema,\n} from \"./query-builder\";\n\nexport function createModel<\n  Doc extends Document,\n  S extends SchemaDefinition = SchemaDefinition,\n>(schema: S, monkoClient: MonkoClient): Model<Doc> {\n  const coll = monkoClient.client.db(schema.db).collection<Doc>(schema.collection);\n\n  // Register the schema for populate functionality\n  registerSchema(schema);\n\n  const toJSON = (doc: WithId<Doc>) => {\n    return JSON.parse(JSON.stringify(doc));\n  };\n\n  return {\n    find(filter: Filter<Doc> = {}): QueryBuilder<Doc> {\n      return new QueryBuilderImpl<Doc>(\n        coll,\n        schema,\n        monkoClient,\n        filter,\n        toJSON,\n      );\n    },\n\n    findOne(filter: Filter<Doc>): SingleQueryBuilder<Doc> {\n      return new SingleQueryBuilderImpl<Doc>(\n        coll,\n        schema,\n        monkoClient,\n        filter,\n        toJSON,\n      );\n    },\n\n    update(filter: Filter<Doc>, update: UpdateFilter<Doc>) {\n      // Note: Timestamp functionality is currently limited by MongoDB's strict TypeScript definitions\n      // The user's Doc type should include createdAt/updatedAt fields when timestamps are enabled\n      // but MongoDB's UpdateFilter type doesn't recognize dynamically added fields\n\n      // For now, we disable timestamps in update operations to maintain type safety\n      // TODO: Implement proper timestamp support when MongoDB's types are more flexible\n      return coll.updateOne(filter, update);\n    },\n\n    create(doc: CreateDocument<Doc>) {\n      // Apply field transformations (e.g., convert strings to ObjectIds for objectId fields)\n      const transformedDoc = { ...doc } as Record<string, unknown>;\n\n      for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {\n        if (fieldDef.transform && transformedDoc[fieldName] !== undefined) {\n          const value = transformedDoc[fieldName];\n          // Apply transform if value is a string (as expected by transform function)\n          if (typeof value === 'string') {\n            transformedDoc[fieldName] = fieldDef.transform(value);\n          }\n        }\n      }\n\n      // Use untyped collection to avoid type assertion issues\n      const untypedColl = monkoClient.client.db(schema.db).collection(schema.collection);\n      return untypedColl.insertOne(transformedDoc);\n    },\n\n    delete(filter: Filter<Doc>) {\n      return coll.deleteOne(filter);\n    },\n\n    toJSON,\n  };\n}"],"mappings":"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","names":[]}