import express, { NextFunction, Request, Response } from "express"; import mongoose, { Document, Model } from "mongoose"; import { User } from "./auth"; import { RESTPermissions } from "./permissions"; import { PopulatePath } from "./populate"; import { FernsTransformer } from "./transformers"; export type JSONPrimitive = string | number | boolean | null; export interface JSONArray extends Array { } export type JSONObject = { [member: string]: JSONValue; }; export type JSONValue = JSONPrimitive | JSONObject | JSONArray; export declare function addPopulateToQuery(builtQuery: mongoose.Query, any>, populatePaths?: PopulatePath[]): mongoose.Query, any, "find", Record>; /** * @param a - the first number * @param b - the second number * @returns The sum of `a` and `b` */ export type RESTMethod = "list" | "create" | "read" | "update" | "delete"; /** * This is the main configuration. * @param T - the base document type. This should not include Mongoose models, just the types of the object. */ export interface FernsRouterOptions { /** * A group of method-level (create/read/update/delete/list) permissions. * Determine if the user can perform the operation at all, and for read/update/delete methods, * whether the user can perform the operation on the object referenced. * */ permissions: RESTPermissions; /** * Allow anonymous users to access the resource. * Defaults to false. */ allowAnonymous?: boolean; /** * A list of fields on the model that can be queried using standard comparisons for booleans, * strings, dates * (as ISOStrings), and numbers. * For example: * ?foo=true // boolean query * ?foo=bar // string query * ?foo=1 // number query * ?foo=2022-07-23T02:34:07.118Z // date query (should first be encoded for query params, not shown here) * Note: `limit` and `page` are automatically supported and are reserved. */ queryFields?: string[]; /** * queryFilter is a function to parse the query params and see if the query should be allowed. * This can be used for permissioning to make sure less privileged users are not making * privileged queries. If a query should not be allowed, * return `null` from the function and an empty query result will be returned to the client * without an error. You can also throw an APIError to be explicit about the issues. * You can transform the given query params by returning different values. * If the query is acceptable as-is, return `query` as-is. */ queryFilter?: (user?: User, query?: Record) => Record | null | Promise | null>; /** * Transformers allow data to be transformed before actions are executed, * and serialized before being returned to the user. * * Transformers can be used to throw out fields that the user should not be able to write to, such as the `admin` flag. * Serializers can be used to hide data from the client or change how it is presented. Serializers run after the data * has been changed or queried but before returning to the client. * @deprecated Use preCreate/preUpdate/preDelete hooks instead of transformer.transform. Use serialize instead of * transformer.serialize. * */ transformer?: FernsTransformer; /** Default sort for list operations. Can be a single field, a space-seperated list of fields, or an object. * ?sort=foo // single field: foo ascending * ?sort=-foo // single field: foo descending * ?sort=-foo bar // multi field: foo descending, bar ascending * ?sort=\{foo: 'ascending', bar: 'descending'\} // object: foo ascending, bar descending * * Note: you should have an index field on these fields or Mongo may slow down considerably. * */ sort?: string | { [key: string]: "ascending" | "descending"; }; /** * Default queries to provide to Mongo before any user queries or transforms happen when making * list queries. Accepts any Mongoose-style queries, and runs for all user types. * defaultQueryParams: \{hidden: false\} // By default, don't show objects with hidden=true * These can be overridden by the user if not disallowed by queryFilter. */ defaultQueryParams?: { [key: string]: any; }; /** * Manages Mongoose populations before returning from all methods (list, read, create, etc). * For each population: * path: Accepts Mongoose-style populate strings for path. e.g. "user" or "users.userId" * (for an array of subschemas with userId) * fields: An array of strings to filter on the populated objects, following Mongoose's select * rules. If each field starts a preceding "-", will act as a block list and only remove those * fields. If each field does not start with a "-", will act as an allow list and only * return those fields. Mixing allow and blocking is not supported. e.g. "-created updated" * is an error. * openApiComponent: If you have a component already registered, * use that instead of autogenerating the types for the populated fields. * */ populatePaths?: PopulatePath[]; /** Default limit applied to list queries if not specified by the user. Defaults to 100. */ defaultLimit?: number; /** * Maximum query limit the user can request. Defaults to 500, and is the lowest of the limit * query, max limit, * or 500. */ maxLimit?: number; /** */ endpoints?: (router: any) => void; /** * Hook that runs after `transformer.transform` but before the object is created. * Can update the body fields based on the request or the user. * Return null to return a generic 403 error. Throw an APIError to return a 400 with specific * error information. */ preCreate?: (value: any, request: express.Request) => T | Promise | null; /** * Hook that runs after `transformer.transform` but before changes are made for update operations. * Can update the body fields based on the request or the user. * Also applies to all array operations. Return null to return a generic 403 error. * Throw an APIError to return a 400 with specific error information. * * @param value - The request body relative to the model update (type: Partial). Note: this does not contain the entire document to be updated, only the fields being updated. * @param request - The Express request object. */ preUpdate?: (value: Partial, request: express.Request) => T | Promise | null; /** * Hook that runs after `transformer.transform` but before the object is deleted. * Return null to return a generic 403 error. * Throw an APIError to return a 400 with specific error information. * * @param value - The document to be deleted, before the soft update of deleted: true (type: T). * @param request - The Express request object. */ preDelete?: (value: T, request: express.Request) => T | Promise | null; /** * Hook that runs after the object is created but before the responseHandler serializes and * returned. This is a good spot to perform dependent changes to other models or performing async * tasks/side effects, such as sending a push notification. * Throw an APIError to return a 400 with an error message. */ postCreate?: (value: T, request: express.Request) => void | Promise; /** * Hook that runs after the object is updated but before the responseHandler serializes and * returned. This is a good spot to perform dependent changes to other models or perform async * tasks/side effects, such as sending a push notification. * Throw an APIError to return a 400 with an error message. * * @param value - The document after it has been updated (type: T). * @param cleanedBody - The request body relative to the model update (type: Partial). * @param request - The Express request object. * @param prevValue - The entire document before it was updated (type: T). */ postUpdate?: (value: T, cleanedBody: Partial, request: express.Request, prevValue: T) => void | Promise; /** * Hook that runs after the object is deleted. This is a good spot to perform dependent changes * to other models or performing async tasks/side effects, such as cascading object deletions. * Throw an APIError to return a 400 with an error message. * * @param request - The Express request object. * @param value - The document that was deleted, after the soft update of deleted: true (type: T). */ postDelete?: (request: express.Request, value: T) => void | Promise; /** Hook that runs after the object is fetched but before it is serialized. * Returns a promise so that asynchronous actions can be included in the function. * Throw an APIError to return a 400 with an error message. * @deprecated: Use responseHandler instead. */ postGet?: (value: T, request: express.Request) => undefined | Promise; /** Hook that runs after the list of objects is fetched but before they are serialized. * Returns a promise so that asynchronous actions can be included in the function. * Throw an APIError to return a 400 with an error message. * @deprecated: Use responseHandler instead. */ postList?: (value: (Document & T)[], request: express.Request) => Promise<(Document & T)[]>; /** * Serialize an object or list of objects before returning to the client. * This is a good spot to remove sensitive information from the object, such as passwords or API * keys. Throw an APIError to return a 400 with an error message. */ responseHandler?: (value: (Document & T) | (Document & T)[], method: "list" | "create" | "read" | "update" | "delete", request: express.Request, options: FernsRouterOptions) => Promise; /** * The discriminatorKey that you passed when creating the Mongoose models. Defaults to __t. See: * https://mongoosejs.com/docs/discriminators.html If this key is provided, * you must provide the same key as part of the top level of the body when making performing * update or delete operations on this model. * \{discriminatorKey: "__t"\} * * PATCH \{__t: "SuperUser", name: "Foo"\} // __t is required or there will be a 404 error. */ discriminatorKey?: string; /** * The OpenAPI generator for this server. This is used to generate the OpenAPI documentation. */ openApi?: any; /** * Overwrite parts of the configuration for the OpenAPI generator. * This will be merged with the generated configuration. */ openApiOverwrite?: { get?: any; list?: any; create?: any; update?: any; delete?: any; }; /** * Overwrite parts of the model properties for the OpenAPI generator. * This will be merged with the generated configuration. * This is useful if you add custom properties to the model during serialize, for example, * that you want to be documented and typed in the SDK. */ openApiExtraModelProperties?: any; } export declare function getModel(baseModel: Model, body?: any, options?: FernsRouterOptions): mongoose.Model; /** * Create a set of CRUD routes given a Mongoose model $baseModel and configuration options. * * @param baseModel A Mongoose Model * @param options Options for configuring the REST API, such as permissions, transformers, and hooks. */ export declare function fernsRouter(baseModel: Model, options: FernsRouterOptions): express.Router; export declare const asyncHandler: (fn: any) => (req: Request, res: Response, next: NextFunction) => Promise; export declare const gooseRestRouter: typeof fernsRouter; export type GooseRESTOptions = FernsRouterOptions;