UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

647 lines (646 loc) • 30.6 kB
// @ts-strict-ignore import { spawn } from 'child_process'; import crypto from 'crypto'; import { fileURLToPath } from 'node:url'; import path from 'path'; import WebSocket from 'ws'; import { Cron } from 'croner'; import { decrypt, encrypt } from './auth/crypto.js'; import { APIKeyAuthentication } from './auth/strategies/apiKey.js'; import { JWTAuthentication } from './auth/strategies/jwt.js'; import { generateImportMap } from './bin/generateImportMap/index.js'; import { checkPayloadDependencies } from './checkPayloadDependencies.js'; import localOperations from './collections/operations/local/index.js'; import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'; import { fieldAffectsData } from './fields/config/types.js'; import localGlobalOperations from './globals/operations/local/index.js'; import { getJobsLocalAPI } from './queues/localAPI.js'; import { isNextBuild } from './utilities/isNextBuild.js'; import { getLogger } from './utilities/logger.js'; import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'; import { traverseFields } from './utilities/traverseFields.js'; export { default as executeAccess } from './auth/executeAccess.js'; export { executeAuthStrategies } from './auth/executeAuthStrategies.js'; const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); let checkedDependencies = false; /** * @description Payload */ export class BasePayload { /** * @description Authorization and Authentication using headers and cookies to run auth user strategies * @returns permissions: Permissions * @returns user: User */ auth = async (options)=>{ const { auth } = localOperations.auth; return auth(this, options); }; authStrategies; blocks = {}; collections = {}; config; /** * @description Performs count operation * @param options * @returns count of documents satisfying query */ count = async (options)=>{ const { count } = localOperations; return count(this, options); }; /** * @description Performs countGlobalVersions operation * @param options * @returns count of global document versions satisfying query */ countGlobalVersions = async (options)=>{ const { countGlobalVersions } = localGlobalOperations; return countGlobalVersions(this, options); }; /** * @description Performs countVersions operation * @param options * @returns count of document versions satisfying query */ countVersions = async (options)=>{ const { countVersions } = localOperations; return countVersions(this, options); }; /** * @description Performs create operation * @param options * @returns created document */ create = async (options)=>{ const { create } = localOperations; return create(this, options); }; db; decrypt = decrypt; duplicate = async (options)=>{ const { duplicate } = localOperations; return duplicate(this, options); }; email; encrypt = encrypt; // TODO: re-implement or remove? // errorHandler: ErrorHandler extensions; /** * @description Find documents with criteria * @param options * @returns documents satisfying query */ find = async (options)=>{ const { find } = localOperations; return find(this, options); }; /** * @description Find document by ID * @param options * @returns document with specified ID */ findByID = async (options)=>{ const { findByID } = localOperations; return findByID(this, options); }; findGlobal = async (options)=>{ const { findOne } = localGlobalOperations; return findOne(this, options); }; /** * @description Find global version by ID * @param options * @returns global version with specified ID */ findGlobalVersionByID = async (options)=>{ const { findVersionByID } = localGlobalOperations; return findVersionByID(this, options); }; /** * @description Find global versions with criteria * @param options * @returns versions satisfying query */ findGlobalVersions = async (options)=>{ const { findVersions } = localGlobalOperations; return findVersions(this, options); }; /** * @description Find version by ID * @param options * @returns version with specified ID */ findVersionByID = async (options)=>{ const { findVersionByID } = localOperations; return findVersionByID(this, options); }; /** * @description Find versions with criteria * @param options * @returns versions satisfying query */ findVersions = async (options)=>{ const { findVersions } = localOperations; return findVersions(this, options); }; forgotPassword = async (options)=>{ const { forgotPassword } = localOperations.auth; return forgotPassword(this, options); }; getAdminURL = ()=>`${this.config.serverURL}${this.config.routes.admin}`; getAPIURL = ()=>`${this.config.serverURL}${this.config.routes.api}`; globals; importMap; jobs = getJobsLocalAPI(this); logger; login = async (options)=>{ const { login } = localOperations.auth; return login(this, options); }; resetPassword = async (options)=>{ const { resetPassword } = localOperations.auth; return resetPassword(this, options); }; /** * @description Restore global version by ID * @param options * @returns version with specified ID */ restoreGlobalVersion = async (options)=>{ const { restoreVersion } = localGlobalOperations; return restoreVersion(this, options); }; /** * @description Restore version by ID * @param options * @returns version with specified ID */ restoreVersion = async (options)=>{ const { restoreVersion } = localOperations; return restoreVersion(this, options); }; schema; secret; sendEmail; types; unlock = async (options)=>{ const { unlock } = localOperations.auth; return unlock(this, options); }; updateGlobal = async (options)=>{ const { update } = localGlobalOperations; return update(this, options); }; validationRules; verifyEmail = async (options)=>{ const { verifyEmail } = localOperations.auth; return verifyEmail(this, options); }; versions = {}; async bin({ args, cwd, log }) { return new Promise((resolve, reject)=>{ const spawned = spawn('node', [ path.resolve(dirname, '../bin.js'), ...args ], { cwd, stdio: log || log === undefined ? 'inherit' : 'ignore' }); spawned.on('exit', (code)=>{ resolve({ code }); }); spawned.on('error', (error)=>{ reject(error); }); }); } delete(options) { const { deleteLocal } = localOperations; return deleteLocal(this, options); } /** * @description Initializes Payload * @param options */ async init(options) { if (process.env.NODE_ENV !== 'production' && process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true' && !checkedDependencies) { checkedDependencies = true; void checkPayloadDependencies(); } this.importMap = options.importMap; if (!options?.config) { throw new Error('Error: the payload config is required to initialize payload.'); } this.config = await options.config; this.logger = getLogger('payload', this.config.logger); if (!this.config.secret) { throw new Error('Error: missing secret key. A secret key is needed to secure Payload.'); } this.secret = crypto.createHash('sha256').update(this.config.secret).digest('hex').slice(0, 32); this.globals = { config: this.config.globals }; for (const collection of this.config.collections){ let customIDType = undefined; const findCustomID = ({ field })=>{ if ([ 'array', 'blocks', 'group' ].includes(field.type) || field.type === 'tab' && 'name' in field) { return true; } if (!fieldAffectsData(field)) { return; } if (field.name === 'id') { customIDType = field.type; return true; } }; traverseFields({ callback: findCustomID, config: this.config, fields: collection.fields, parentIsLocalized: false }); this.collections[collection.slug] = { config: collection, customIDType }; } this.blocks = this.config.blocks.reduce((blocks, block)=>{ blocks[block.slug] = block; return blocks; }, {}); // Generate types on startup if (process.env.NODE_ENV !== 'production' && this.config.typescript.autoGenerate !== false) { // We cannot run it directly here, as generate-types imports json-schema-to-typescript, which breaks on turbopack. // see: https://github.com/vercel/next.js/issues/66723 void this.bin({ args: [ 'generate:types' ], log: false }); } this.db = this.config.db.init({ payload: this }); this.db.payload = this; if (this.db?.init) { await this.db.init(); } if (!options.disableDBConnect && this.db.connect) { await this.db.connect(); } // Load email adapter if (this.config.email instanceof Promise) { const awaitedAdapter = await this.config.email; this.email = awaitedAdapter({ payload: this }); } else if (this.config.email) { this.email = this.config.email({ payload: this }); } else { if (process.env.NEXT_PHASE !== 'phase-production-build') { this.logger.warn(`No email adapter provided. Email will be written to console. More info at https://payloadcms.com/docs/email/overview.`); } this.email = consoleEmailAdapter({ payload: this }); } // Warn if image resizing is enabled but sharp is not installed if (!this.config.sharp && this.config.collections.some((c)=>c.upload.imageSizes || c.upload.formatOptions)) { this.logger.warn(`Image resizing is enabled for one or more collections, but sharp not installed. Please install 'sharp' and pass into the config.`); } // Warn if user is deploying to Vercel, and any upload collection is missing a storage adapter if (process.env.VERCEL) { const uploadCollWithoutAdapter = this.config.collections.filter((c)=>c.upload && c.upload.adapter === undefined); if (uploadCollWithoutAdapter.length) { const slugs = uploadCollWithoutAdapter.map((c)=>c.slug).join(', '); this.logger.warn(`Collections with uploads enabled require a storage adapter when deploying to Vercel. Collection(s) without storage adapters: ${slugs}. See https://payloadcms.com/docs/upload/storage-adapters for more info.`); } } this.sendEmail = this.email['sendEmail']; serverInitTelemetry(this); // 1. loop over collections, if collection has auth strategy, initialize and push to array let jwtStrategyEnabled = false; this.authStrategies = this.config.collections.reduce((authStrategies, collection)=>{ if (collection?.auth) { if (collection.auth.strategies.length > 0) { authStrategies.push(...collection.auth.strategies); } // 2. if api key enabled, push api key strategy into the array if (collection.auth?.useAPIKey) { authStrategies.push({ name: `${collection.slug}-api-key`, authenticate: APIKeyAuthentication(collection) }); } // 3. if localStrategy flag is true if (!collection.auth.disableLocalStrategy && !jwtStrategyEnabled) { jwtStrategyEnabled = true; } } return authStrategies; }, []); // 4. if enabled, push jwt strategy into authStrategies last if (jwtStrategyEnabled) { this.authStrategies.push({ name: 'local-jwt', authenticate: JWTAuthentication }); } try { if (!options.disableOnInit) { if (typeof options.onInit === 'function') { await options.onInit(this); } if (typeof this.config.onInit === 'function') { await this.config.onInit(this); } } } catch (error) { this.logger.error({ err: error }, 'Error running onInit function'); throw error; } if (this.config.jobs.autoRun && !isNextBuild()) { const DEFAULT_CRON = '* * * * *'; const DEFAULT_LIMIT = 10; const cronJobs = typeof this.config.jobs.autoRun === 'function' ? await this.config.jobs.autoRun(this) : this.config.jobs.autoRun; await Promise.all(cronJobs.map((cronConfig)=>{ const job = new Cron(cronConfig.cron ?? DEFAULT_CRON, async ()=>{ if (typeof this.config.jobs.shouldAutoRun === 'function') { const shouldAutoRun = await this.config.jobs.shouldAutoRun(this); if (!shouldAutoRun) { job.stop(); return false; } } await this.jobs.run({ limit: cronConfig.limit ?? DEFAULT_LIMIT, queue: cronConfig.queue }); }); })); } return this; } update(options) { const { update } = localOperations; return update(this, options); } } const initialized = new BasePayload(); export default initialized; let cached = global._payload; if (!cached) { cached = global._payload = { payload: null, promise: null, reload: false, ws: null }; } export const reload = async (config, payload, skipImportMapGeneration)=>{ if (typeof payload.db.destroy === 'function') { await payload.db.destroy(); } payload.config = config; payload.collections = config.collections.reduce((collections, collection)=>{ collections[collection.slug] = { config: collection, customIDType: payload.collections[collection.slug]?.customIDType }; return collections; }, {}); payload.blocks = config.blocks.reduce((blocks, block)=>{ blocks[block.slug] = block; return blocks; }, {}); payload.globals = { config: config.globals }; // TODO: support HMR for other props in the future (see payload/src/index init()) that may change on Payload singleton // Generate types if (config.typescript.autoGenerate !== false) { // We cannot run it directly here, as generate-types imports json-schema-to-typescript, which breaks on turbopack. // see: https://github.com/vercel/next.js/issues/66723 void payload.bin({ args: [ 'generate:types' ], log: false }); } // Generate component map if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) { await generateImportMap(config, { log: true }); } await payload.db.init(); if (payload.db.connect) { await payload.db.connect({ hotReload: true }); } global._payload_clientConfigs = {}; global._payload_schemaMap = null; global._payload_clientSchemaMap = null; global._payload_doNotCacheClientConfig = true // This will help refreshing the client config cache more reliably. If you remove this, please test HMR + client config refreshing (do new fields appear in the document?) ; global._payload_doNotCacheSchemaMap = true; global._payload_doNotCacheClientSchemaMap = true; }; export const getPayload = async (options)=>{ if (!options?.config) { throw new Error('Error: the payload config is required for getPayload to work.'); } if (cached.payload) { if (cached.reload === true) { let resolve; // getPayload is called multiple times, in parallel. However, we only want to run `await reload` once. By immediately setting cached.reload to a promise, // we can ensure that all subsequent calls will wait for the first reload to finish. So if we set it here, the 2nd call of getPayload // will reach `if (cached.reload instanceof Promise) {` which then waits for the first reload to finish. cached.reload = new Promise((res)=>resolve = res); const config = await options.config; await reload(config, cached.payload, !options.importMap); resolve(); } if (cached.reload instanceof Promise) { await cached.reload; } if (options?.importMap) { cached.payload.importMap = options.importMap; } return cached.payload; } // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!cached.promise) { // no need to await options.config here, as it's already awaited in the BasePayload.init cached.promise = new BasePayload().init(options); } try { cached.payload = await cached.promise; if (!cached.ws && process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && process.env.DISABLE_PAYLOAD_HMR !== 'true') { try { const port = process.env.PORT || '3000'; const path = '/_next/webpack-hmr'; // The __NEXT_ASSET_PREFIX env variable is set for both assetPrefix and basePath (tested in Next.js 15.1.6) const prefix = process.env.__NEXT_ASSET_PREFIX ?? ''; cached.ws = new WebSocket(process.env.PAYLOAD_HMR_URL_OVERRIDE ?? `ws://localhost:${port}${prefix}${path}`); cached.ws.onmessage = (event)=>{ if (typeof event.data === 'string') { const data = JSON.parse(event.data); if ('action' in data && data.action === 'serverComponentChanges') { cached.reload = true; } } }; cached.ws.onerror = (_)=>{ // swallow any websocket connection error }; } catch (_) { // swallow e } } } catch (e) { cached.promise = null; // add identifier to error object, so that our error logger in routeError.ts does not attempt to re-initialize getPayload e.payloadInitError = true; throw e; } if (options?.importMap) { cached.payload.importMap = options.importMap; } return cached.payload; }; export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'; export { getAccessResults } from './auth/getAccessResults.js'; export { getFieldsToSign } from './auth/getFieldsToSign.js'; export * from './auth/index.js'; export { accessOperation } from './auth/operations/access.js'; export { forgotPasswordOperation } from './auth/operations/forgotPassword.js'; export { initOperation } from './auth/operations/init.js'; export { loginOperation } from './auth/operations/login.js'; export { logoutOperation } from './auth/operations/logout.js'; export { meOperation } from './auth/operations/me.js'; export { refreshOperation } from './auth/operations/refresh.js'; export { registerFirstUserOperation } from './auth/operations/registerFirstUser.js'; export { resetPasswordOperation } from './auth/operations/resetPassword.js'; export { unlockOperation } from './auth/operations/unlock.js'; export { verifyEmailOperation } from './auth/operations/verifyEmail.js'; export { JWTAuthentication } from './auth/strategies/jwt.js'; export { generateImportMap } from './bin/generateImportMap/index.js'; export { genImportMapIterateFields } from './bin/generateImportMap/iterateFields.js'; export { createClientCollectionConfig, createClientCollectionConfigs } from './collections/config/client.js'; export { createDataloaderCacheKey, getDataLoader } from './collections/dataloader.js'; export { countOperation } from './collections/operations/count.js'; export { createOperation } from './collections/operations/create.js'; export { deleteOperation } from './collections/operations/delete.js'; export { deleteByIDOperation } from './collections/operations/deleteByID.js'; export { docAccessOperation } from './collections/operations/docAccess.js'; export { duplicateOperation } from './collections/operations/duplicate.js'; export { findOperation } from './collections/operations/find.js'; export { findByIDOperation } from './collections/operations/findByID.js'; export { findVersionByIDOperation } from './collections/operations/findVersionByID.js'; export { findVersionsOperation } from './collections/operations/findVersions.js'; export { restoreVersionOperation } from './collections/operations/restoreVersion.js'; export { updateOperation } from './collections/operations/update.js'; export { updateByIDOperation } from './collections/operations/updateByID.js'; export { buildConfig } from './config/build.js'; export { createClientConfig, serverOnlyAdminConfigProperties, serverOnlyConfigProperties } from './config/client.js'; export { defaults } from './config/defaults.js'; export { sanitizeConfig } from './config/sanitize.js'; export { combineQueries } from './database/combineQueries.js'; export { createDatabaseAdapter } from './database/createDatabaseAdapter.js'; export { defaultBeginTransaction } from './database/defaultBeginTransaction.js'; export { flattenWhereToOperators } from './database/flattenWhereToOperators.js'; export { getLocalizedPaths } from './database/getLocalizedPaths.js'; export { createMigration } from './database/migrations/createMigration.js'; export { getMigrations } from './database/migrations/getMigrations.js'; export { getPredefinedMigration } from './database/migrations/getPredefinedMigration.js'; export { migrate } from './database/migrations/migrate.js'; export { migrateDown } from './database/migrations/migrateDown.js'; export { migrateRefresh } from './database/migrations/migrateRefresh.js'; export { migrateReset } from './database/migrations/migrateReset.js'; export { migrateStatus } from './database/migrations/migrateStatus.js'; export { migrationsCollection } from './database/migrations/migrationsCollection.js'; export { migrationTemplate } from './database/migrations/migrationTemplate.js'; export { readMigrationFiles } from './database/migrations/readMigrationFiles.js'; export { writeMigrationIndex } from './database/migrations/writeMigrationIndex.js'; export { validateQueryPaths } from './database/queryValidation/validateQueryPaths.js'; export { validateSearchParam } from './database/queryValidation/validateSearchParams.js'; export { APIError, APIErrorName, AuthenticationError, DuplicateCollection, DuplicateFieldName, DuplicateGlobal, ErrorDeletingFile, FileRetrievalError, FileUploadError, Forbidden, InvalidConfiguration, InvalidFieldName, InvalidFieldRelationship, Locked, LockedAuth, MissingCollectionLabel, MissingEditorProp, MissingFieldInputOptions, MissingFieldType, MissingFile, NotFound, QueryError, ValidationError, ValidationErrorName } from './errors/index.js'; export { baseBlockFields } from './fields/baseFields/baseBlockFields.js'; export { baseIDField } from './fields/baseFields/baseIDField.js'; export { createClientField, createClientFields } from './fields/config/client.js'; export { sanitizeFields } from './fields/config/sanitize.js'; export { getDefaultValue } from './fields/getDefaultValue.js'; export { traverseFields as afterChangeTraverseFields } from './fields/hooks/afterChange/traverseFields.js'; export { promise as afterReadPromise } from './fields/hooks/afterRead/promise.js'; export { traverseFields as afterReadTraverseFields } from './fields/hooks/afterRead/traverseFields.js'; export { traverseFields as beforeChangeTraverseFields } from './fields/hooks/beforeChange/traverseFields.js'; export { traverseFields as beforeValidateTraverseFields } from './fields/hooks/beforeValidate/traverseFields.js'; export { default as sortableFieldTypes } from './fields/sortableFieldTypes.js'; export { validations } from './fields/validations.js'; export { createClientGlobalConfig, createClientGlobalConfigs } from './globals/config/client.js'; export { docAccessOperation as docAccessOperationGlobal } from './globals/operations/docAccess.js'; export { findOneOperation } from './globals/operations/findOne.js'; export { findVersionByIDOperation as findVersionByIDOperationGlobal } from './globals/operations/findVersionByID.js'; export { findVersionsOperation as findVersionsOperationGlobal } from './globals/operations/findVersions.js'; export { restoreVersionOperation as restoreVersionOperationGlobal } from './globals/operations/restoreVersion.js'; export { updateOperation as updateOperationGlobal } from './globals/operations/update.js'; export { jobAfterRead } from './queues/config/index.js'; export { importHandlerPath } from './queues/operations/runJobs/runJob/importHandlerPath.js'; export { getLocalI18n } from './translations/getLocalI18n.js'; export * from './types/index.js'; export { getFileByPath } from './uploads/getFileByPath.js'; export { addDataAndFileToRequest } from './utilities/addDataAndFileToRequest.js'; export { addLocalesToRequestFromData, sanitizeLocales } from './utilities/addLocalesToRequest.js'; export { commitTransaction } from './utilities/commitTransaction.js'; export { configToJSONSchema, entityToJSONSchema, fieldsToJSONSchema, withNullableJSONSchemaType } from './utilities/configToJSONSchema.js'; export { createArrayFromCommaDelineated } from './utilities/createArrayFromCommaDelineated.js'; export { createLocalReq } from './utilities/createLocalReq.js'; export { createPayloadRequest } from './utilities/createPayloadRequest.js'; export { deepCopyObject, deepCopyObjectComplex, deepCopyObjectSimple } from './utilities/deepCopyObject.js'; export { deepMerge, deepMergeWithCombinedArrays, deepMergeWithReactComponents, deepMergeWithSourceArrays } from './utilities/deepMerge.js'; export { checkDependencies } from './utilities/dependencies/dependencyChecker.js'; export { getDependencies } from './utilities/dependencies/getDependencies.js'; export { findUp, findUpSync, pathExistsAndIsAccessible, pathExistsAndIsAccessibleSync } from './utilities/findUp.js'; export { flattenAllFields } from './utilities/flattenAllFields.js'; export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'; export { formatErrors } from './utilities/formatErrors.js'; export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'; export { getBlockSelect } from './utilities/getBlockSelect.js'; export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'; export { getFieldByPath } from './utilities/getFieldByPath.js'; export { getObjectDotNotation } from './utilities/getObjectDotNotation.js'; export { getRequestLanguage } from './utilities/getRequestLanguage.js'; export { handleEndpoints } from './utilities/handleEndpoints.js'; export { headersWithCors } from './utilities/headersWithCors.js'; export { initTransaction } from './utilities/initTransaction.js'; export { isEntityHidden } from './utilities/isEntityHidden.js'; export { default as isolateObjectProperty } from './utilities/isolateObjectProperty.js'; export { isPlainObject } from './utilities/isPlainObject.js'; export { isValidID } from './utilities/isValidID.js'; export { killTransaction } from './utilities/killTransaction.js'; export { logError } from './utilities/logError.js'; export { defaultLoggerOptions } from './utilities/logger.js'; export { mapAsync } from './utilities/mapAsync.js'; export { mergeHeaders } from './utilities/mergeHeaders.js'; export { sanitizeFallbackLocale } from './utilities/sanitizeFallbackLocale.js'; export { sanitizeJoinParams } from './utilities/sanitizeJoinParams.js'; export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'; export { sanitizeSelectParam } from './utilities/sanitizeSelectParam.js'; export { stripUnselectedFields } from './utilities/stripUnselectedFields.js'; export { traverseFields } from './utilities/traverseFields.js'; export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'; export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'; export { buildVersionCompoundIndexes } from './versions/buildVersionCompoundIndexes.js'; export { versionDefaults } from './versions/defaults.js'; export { deleteCollectionVersions } from './versions/deleteCollectionVersions.js'; export { appendVersionToQueryKey } from './versions/drafts/appendVersionToQueryKey.js'; export { getQueryDraftsSort } from './versions/drafts/getQueryDraftsSort.js'; export { enforceMaxVersions } from './versions/enforceMaxVersions.js'; export { getLatestCollectionVersion } from './versions/getLatestCollectionVersion.js'; export { getLatestGlobalVersion } from './versions/getLatestGlobalVersion.js'; export { saveVersion } from './versions/saveVersion.js'; export { deepMergeSimple } from '@payloadcms/translations/utilities'; //# sourceMappingURL=index.js.map