Compare commits

...

2 Commits

Author SHA1 Message Date
Sasha
ed34783759 scaffolds int test with attached websocket 2024-12-17 15:38:46 +02:00
Sasha
2f146f05f2 feat: add KV storage adapters 2024-12-17 14:23:32 +02:00
41 changed files with 1488 additions and 10 deletions

View File

@@ -202,6 +202,13 @@ jobs:
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:latest
ports:
- 6379:6379 # Expose Redis on port 6379
options: --health-cmd "redis-cli ping" --health-timeout 30s --health-retries 3
steps:
- uses: actions/checkout@v4
@@ -252,6 +259,10 @@ jobs:
echo "POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres" >> $GITHUB_ENV
if: matrix.database == 'supabase'
- name: Configure Redis
run: |
echo "REDIS_URL=redis://127.0.0.1:6379" >> $GITHUB_ENV
- name: Integration Tests
uses: nick-fields/retry@v3
env:

View File

@@ -24,6 +24,7 @@
"build:essentials:force": "pnpm clean:build && turbo build --filter=\"payload...\" --filter=\"@payloadcms/ui\" --filter=\"@payloadcms/next\" --filter=\"@payloadcms/db-mongodb\" --filter=\"@payloadcms/db-postgres\" --filter=\"@payloadcms/richtext-lexical\" --filter=\"@payloadcms/translations\" --filter=\"@payloadcms/plugin-cloud\" --filter=\"@payloadcms/graphql\" --no-cache --force",
"build:force": "pnpm run build:core:force",
"build:graphql": "turbo build --filter \"@payloadcms/graphql\"",
"build:kv-redis": "turbo build --filter \"@payloadcms/kv-redis\"",
"build:live-preview": "turbo build --filter \"@payloadcms/live-preview\"",
"build:live-preview-react": "turbo build --filter \"@payloadcms/live-preview-react\"",
"build:live-preview-vue": "turbo build --filter \"@payloadcms/live-preview-vue\"",

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

15
packages/kv-redis/.swcrc Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "es6"
}
}

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018-2024 Payload CMS, Inc. <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,33 @@
# Redis KV Adapter for Payload (beta)
This package provides a way to use [Redis](https://redis.io) as a KV adapter with Payload.
## Installation
```sh
pnpm add @payloadcms/kv-redis
```
## Usage
```ts
import { redisKVAdapter } from '@payloadcms/kv-redis'
export default buildConfig({
collections: [Media],
kv: redisKVAdapter({
// Redis connection URL. Defaults to process.env.REDIS_URL
redisURL: 'redis://localhost:6379',
// Optional prefix for Redis keys to isolate the store. Defaults to 'payload-kv'
prefix: 'kv-storage',
}),
})
```
Then you can access the KV storage using `payload.kv`:
```ts
await payload.kv.set('key', { value: 1 })
const data = await payload.kv.get('key')
payload.loger.info(data)
```

View File

@@ -0,0 +1,18 @@
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
/** @typedef {import('eslint').Linter.Config} Config */
/** @type {Config[]} */
export const index = [
...rootEslintConfig,
{
languageOptions: {
parserOptions: {
...rootParserOptions,
tsconfigRootDir: import.meta.dirname,
},
},
},
]
export default index

View File

@@ -0,0 +1,66 @@
{
"name": "@payloadcms/kv-redis",
"version": "3.6.0",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/storage-uploadthing"
},
"license": "MIT",
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
"maintainers": [
{
"name": "Payload",
"email": "info@payloadcms.com",
"url": "https://payloadcms.com"
}
],
"type": "module",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "pnpm build:types && pnpm build:swc",
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepublishOnly": "pnpm clean && pnpm turbo build"
},
"dependencies": {
"ioredis": "^5.4.1"
},
"devDependencies": {
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "workspace:*"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"
},
"publishConfig": {
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}

View File

@@ -0,0 +1,79 @@
import type { KVAdapter, KVAdapterResult, KVStoreValue } from 'payload'
import { Redis } from 'ioredis'
export class RedisKVAdapter implements KVAdapter {
redisClient: Redis
constructor(
readonly keyPrefix: string,
redisURL: string,
) {
this.redisClient = new Redis(redisURL)
}
async clear(): Promise<void> {
const keys = await this.redisClient.keys(`${this.keyPrefix}*`)
if (keys.length > 0) {
await this.redisClient.del(keys)
}
}
async delete(key: string): Promise<void> {
await this.redisClient.del(`${this.keyPrefix}${key}`)
}
async get(key: string): Promise<KVStoreValue | null> {
const data = await this.redisClient.get(`${this.keyPrefix}${key}`)
if (data === null) {
return data
}
return JSON.parse(data)
}
async has(key: string): Promise<boolean> {
const exists = await this.redisClient.exists(`${this.keyPrefix}${key}`)
return exists === 1
}
async keys(): Promise<string[]> {
const prefixedKeys = await this.redisClient.keys(`${this.keyPrefix}*`)
if (this.keyPrefix) {
return prefixedKeys.map((key) => key.replace(this.keyPrefix, ''))
}
return prefixedKeys
}
async set(key: string, data: KVStoreValue): Promise<void> {
await this.redisClient.set(`${this.keyPrefix}${key}`, JSON.stringify(data))
}
}
export type RedisKVAdapterOptions = {
/**
* Optional prefix for Redis keys to isolate the store
*
* @default 'payload-kv:'
*/
keyPrefix?: string
/** Redis connection URL (e.g., 'redis://localhost:6379'). Defaults to process.env.REDIS_URL */
redisURL?: string
}
export const redisKVAdapter = (options: RedisKVAdapterOptions = {}): KVAdapterResult => {
const keyPrefix = options.keyPrefix ?? 'payload-kv:'
const redisURL = options.redisURL ?? process.env.REDIS_URL
if (!redisURL) {
throw new Error('redisURL or REDIS_URL env variable is required')
}
return {
init: () => new RedisKVAdapter(keyPrefix, redisURL),
}
}

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true, // Make sure typescript knows that this module depends on their references
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"strict": true
},
"exclude": ["dist", "node_modules"],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
"references": [{ "path": "../payload" }]
}

View File

@@ -27,6 +27,7 @@ export type ServerOnlyRootProperties = keyof Pick<
| 'graphQL'
| 'hooks'
| 'jobs'
| 'kv'
| 'logger'
| 'onInit'
| 'plugins'
@@ -67,6 +68,7 @@ export const serverOnlyConfigProperties: readonly Partial<ServerOnlyRootProperti
'graphQL',
'jobs',
'logger',
'kv',
// `admin`, `onInit`, `localization`, `collections`, and `globals` are all handled separately
]

View File

@@ -2,6 +2,7 @@ import type { JobsConfig } from '../queues/config/types/index.js'
import type { Config } from './types.js'
import defaultAccess from '../auth/defaultAccess.js'
import { databaseKVAdapter } from '../kv/adapters/DatabaseKVAdapter.js'
export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
admin: {
@@ -54,6 +55,7 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
deleteJobOnComplete: true,
depth: 0,
} as JobsConfig,
kv: databaseKVAdapter(),
localization: false,
maxDepth: 10,
routes: {

View File

@@ -192,6 +192,10 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
configWithDefaults.collections.push(getPreferencesCollection(config as unknown as Config))
configWithDefaults.collections.push(migrationsCollection)
if (configWithDefaults.kv.kvCollection) {
configWithDefaults.collections.push(configWithDefaults.kv.kvCollection)
}
const richTextSanitizationPromises: Array<(config: SanitizedConfig) => Promise<void>> = []
for (let i = 0; i < config.collections.length; i++) {
config.collections[i] = await sanitizeCollection(

View File

@@ -38,6 +38,7 @@ import type { EmailAdapter, SendEmailOptions } from '../email/types.js'
import type { ErrorName } from '../errors/types.js'
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
import type { JobsConfig, Payload, RequestContext, TypedUser } from '../index.js'
import type { KVAdapterResult } from '../kv/index.js'
import type { PayloadRequest, Where } from '../types/index.js'
import type { PayloadLogger } from '../utilities/logger.js'
@@ -974,6 +975,15 @@ export type Config = {
* @experimental There may be frequent breaking changes to this API
*/
jobs?: JobsConfig
/**
* Pass in a KV adapter for use on this project.
* @default `DatabaseKVAdapter` from:
* ```ts
* import { createDatabaseKVAdapter } from 'payload'
* createDatabaseKVAdapter()
* ```
*/
kv?: KVAdapterResult
/**
* Translate your content to different languages/locales.
*

View File

@@ -8,7 +8,7 @@ import crypto from 'crypto'
import { fileURLToPath } from 'node:url'
import path from 'path'
import WebSocket from 'ws'
export type { FieldState } from './admin/forms/Form.js'
import type { AuthArgs } from './auth/operations/auth.js'
import type { Result as ForgotPasswordResult } from './auth/operations/forgotPassword.js'
import type { Options as ForgotPasswordOptions } from './auth/operations/local/forgotPassword.js'
@@ -54,6 +54,7 @@ import type { Options as FindGlobalVersionByIDOptions } from './globals/operatio
import type { Options as FindGlobalVersionsOptions } from './globals/operations/local/findVersions.js'
import type { Options as RestoreGlobalVersionOptions } from './globals/operations/local/restoreVersion.js'
import type { Options as UpdateGlobalOptions } from './globals/operations/local/update.js'
import type { KVAdapter } from './kv/index.js'
import type {
ApplyDisableErrors,
JsonObject,
@@ -78,8 +79,8 @@ import { getLogger } from './utilities/logger.js'
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
import { traverseFields } from './utilities/traverseFields.js'
export type { FieldState } from './admin/forms/Form.js'
export type * from './admin/types.js'
export { default as executeAccess } from './auth/executeAccess.js'
export interface GeneratedTypes {
authUntyped: {
@@ -416,6 +417,11 @@ export class BasePayload {
jobs = getJobsLocalAPI(this)
/**
* Key Value storage
*/
kv: KVAdapter
logger: Logger
login = async <TSlug extends CollectionSlug>(
@@ -618,6 +624,8 @@ export class BasePayload {
this.db = this.config.db.init({ payload: this })
this.db.payload = this
this.kv = this.config.kv.init({ payload: this })
if (this.db?.init) {
await this.db.init()
}
@@ -881,7 +889,6 @@ interface RequestContext {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface DatabaseAdapter extends BaseDatabaseAdapter {}
export type { Payload, RequestContext }
export { default as executeAccess } from './auth/executeAccess.js'
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
export { getAccessResults } from './auth/getAccessResults.js'
export { getFieldsToSign } from './auth/getFieldsToSign.js'
@@ -898,7 +905,6 @@ export { registerFirstUserOperation } from './auth/operations/registerFirstUser.
export { resetPasswordOperation } from './auth/operations/resetPassword.js'
export { unlockOperation } from './auth/operations/unlock.js'
export { verifyEmailOperation } from './auth/operations/verifyEmail.js'
export type {
AuthStrategyFunction,
AuthStrategyFunctionArgs,
@@ -919,8 +925,8 @@ export type {
} from './auth/types.js'
export { generateImportMap } from './bin/generateImportMap/index.js'
export type { ImportMap } from './bin/generateImportMap/index.js'
export type { ImportMap } from './bin/generateImportMap/index.js'
export { genImportMapIterateFields } from './bin/generateImportMap/iterateFields.js'
export {
@@ -967,6 +973,7 @@ export type {
TypeWithID,
TypeWithTimestamps,
} from './collections/config/types.js'
export { createDataloaderCacheKey, getDataLoader } from './collections/dataloader.js'
export { countOperation } from './collections/operations/count.js'
export { createOperation } from './collections/operations/create.js'
@@ -988,8 +995,8 @@ export {
serverOnlyAdminConfigProperties,
serverOnlyConfigProperties,
} from './config/client.js'
export { defaults } from './config/defaults.js'
export { sanitizeConfig } from './config/sanitize.js'
export type * from './config/types.js'
export { combineQueries } from './database/combineQueries.js'
@@ -1097,8 +1104,8 @@ export {
ValidationErrorName,
} from './errors/index.js'
export type { ValidationFieldError } from './errors/index.js'
export { baseBlockFields } from './fields/baseFields/baseBlockFields.js'
export { baseIDField } from './fields/baseFields/baseIDField.js'
export {
createClientField,
@@ -1208,16 +1215,16 @@ export type {
ValidateOptions,
ValueWithRelation,
} from './fields/config/types.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 type {
ArrayFieldValidation,
BlocksFieldValidation,
@@ -1249,7 +1256,6 @@ export type {
UploadFieldValidation,
UsernameFieldValidation,
} from './fields/validations.js'
export {
type ClientGlobalConfig,
createClientGlobalConfig,
@@ -1273,9 +1279,14 @@ export type {
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 * from './kv/adapters/DatabaseKVAdapter.js'
export * from './kv/adapters/InMemoryKVAdapter.js'
export * from './kv/index.js'
export type {
CollapsedPreferences,
DocumentPreferences,
@@ -1308,6 +1319,7 @@ export type {
WorkflowTypes,
} from './queues/config/types/workflowTypes.js'
export { importHandlerPath } from './queues/operations/runJobs/runJob/importHandlerPath.js'
export { bindWebsocketToServer } from './realtime/websocket.js'
export { getLocalI18n } from './translations/getLocalI18n.js'
export * from './types/index.js'
export { getFileByPath } from './uploads/getFileByPath.js'

View File

@@ -0,0 +1,129 @@
import type { CollectionConfig } from '../../index.js'
import type { Payload, PayloadRequest } from '../../types/index.js'
import type { KVAdapter, KVAdapterResult, KVStoreValue } from '../index.js'
/** Mocked `req`, we don't need to use transactions, neither we want `createLocalReq` overhead. */
const req = {} as PayloadRequest
export class DatabaseKVAdapter implements KVAdapter {
constructor(
readonly payload: Payload,
readonly collectionSlug: string,
) {}
async clear(): Promise<void> {
await this.payload.db.deleteMany({
collection: this.collectionSlug,
req,
where: {},
})
}
async delete(key: string): Promise<void> {
await this.payload.db.deleteOne({
collection: this.collectionSlug,
req,
where: { key: { equals: key } },
})
}
async get(key: string): Promise<KVStoreValue | null> {
const doc = await this.payload.db.findOne<{
data: KVStoreValue
id: number | string
}>({
collection: this.collectionSlug,
joins: false,
req,
select: {
data: true,
key: true,
},
where: { key: { equals: key } },
})
if (doc === null) {
return null
}
return doc.data
}
async has(key: string): Promise<boolean> {
const { totalDocs } = await this.payload.db.count({
collection: this.collectionSlug,
req,
where: { key: { equals: key } },
})
return totalDocs > 0
}
async keys(): Promise<string[]> {
const result = await this.payload.db.find<{ key: string }>({
collection: this.collectionSlug,
limit: 0,
pagination: false,
req,
select: {
key: true,
},
})
return result.docs.map((each) => each.key)
}
async set(key: string, data: KVStoreValue): Promise<void> {
await this.payload.db.upsert({
collection: this.collectionSlug,
data: {
data,
key,
},
joins: false,
req,
select: {},
where: { key: { equals: key } },
})
}
}
export type DatabaseKVAdapterOptions = {
/** Override options for the generated collection */
kvCollectionOverrides?: Partial<CollectionConfig>
}
export const databaseKVAdapter = (options: DatabaseKVAdapterOptions = {}): KVAdapterResult => {
const collectionSlug = options.kvCollectionOverrides?.slug ?? 'payload-kv'
return {
init: ({ payload }) => new DatabaseKVAdapter(payload, collectionSlug),
kvCollection: {
slug: collectionSlug,
access: {
create: () => false,
delete: () => false,
read: () => false,
update: () => false,
},
admin: {
hidden: true,
},
fields: [
{
name: 'key',
type: 'text',
index: true,
required: true,
unique: true,
},
{
name: 'data',
type: 'json',
required: true,
},
],
timestamps: false,
...options.kvCollectionOverrides,
},
}
}

View File

@@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/require-await */
import type { KVAdapter, KVAdapterResult, KVStoreValue } from '../index.js'
export class InMemoryKVAdapter implements KVAdapter {
store = new Map<string, KVStoreValue>()
async clear(): Promise<void> {
this.store.clear()
}
async delete(key: string): Promise<void> {
this.store.delete(key)
}
async get(key: string): Promise<KVStoreValue | null> {
const value = this.store.get(key)
if (typeof value === 'undefined') {
return null
}
return value
}
async has(key: string): Promise<boolean> {
return this.store.has(key)
}
async keys(): Promise<string[]> {
return Array.from(this.store.keys())
}
async set(key: string, value: KVStoreValue): Promise<void> {
this.store.set(key, value)
}
}
export const inMemoryKVAdapter = (): KVAdapterResult => {
return {
init: () => new InMemoryKVAdapter(),
}
}

View File

@@ -0,0 +1,54 @@
import type { CollectionConfig } from '../collections/config/types.js'
import type { Payload } from '../types/index.js'
export type KVStoreValue = NonNullable<unknown>
export interface KVAdapter {
/**
* Clears all entries in the store.
* @returns A promise that resolves once the store is cleared.
*/
clear(): Promise<void>
/**
* Deletes a value from the store by its key.
* @param key - The key to delete.
* @returns A promise that resolves once the key is deleted.
*/
delete(key: string): Promise<void>
/**
* Retrieves a value from the store by its key.
* @param key - The key to look up.
* @returns A promise that resolves to the value, or `null` if not found.
*/
get(key: string): Promise<KVStoreValue | null>
/**
* Checks if a key exists in the store.
* @param key - The key to check.
* @returns A promise that resolves to `true` if the key exists, otherwise `false`.
*/
has(key: string): Promise<boolean>
/**
* Retrieves all the keys in the store.
* @returns A promise that resolves to an array of keys.
*/
keys(): Promise<string[]>
/**
* Sets a value in the store with the given key.
* @param key - The key to associate with the value.
* @param value - The value to store.
* @returns A promise that resolves once the value is stored.
*/
set(key: string, value: KVStoreValue): Promise<void>
}
export interface KVAdapterResult {
init(args: { payload: Payload }): KVAdapter
/** Adapter can create additional collection if needed */
kvCollection?: CollectionConfig
}

View File

@@ -0,0 +1,7 @@
// Use strict: true for new features. This overrides options only for this directory.
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"strict": true
}
}

View File

@@ -0,0 +1,43 @@
import type { Server } from 'http'
import type WebSocketType from 'ws'
import { createRequire } from 'module'
import { parse } from 'url'
import type { SanitizedConfig } from '../config/types.js'
import { getPayload } from '../index.js'
const require = createRequire(import.meta.url)
const WebSocket = require('ws') as typeof WebSocketType
export const bindWebsocketToServer = async ({
config,
server,
}: {
config: Promise<SanitizedConfig> | SanitizedConfig
server: Server
}) => {
const wss = new WebSocket.Server({ noServer: true })
const payload = await getPayload({ config })
wss.on('connection', (ws) => {
console.log('incoming connection', ws)
ws.onclose = () => {
console.log('connection closed', wss.clients.size)
}
})
server.on('upgrade', function (req, socket, head) {
const { pathname } = parse(req.url, true)
if (pathname !== '/_next/webpack-hmr') {
wss.handleUpgrade(req, socket, head, function done(ws) {
wss.emit('connection', ws, req)
})
}
})
payload.logger.info(`Bound WSS`)
}

77
pnpm-lock.yaml generated
View File

@@ -637,6 +637,16 @@ importers:
specifier: workspace:*
version: link:../payload
packages/kv-redis:
dependencies:
ioredis:
specifier: ^5.4.1
version: 5.4.1
devDependencies:
payload:
specifier: workspace:*
version: link:../payload
packages/live-preview:
devDependencies:
'@payloadcms/eslint-config':
@@ -1643,6 +1653,9 @@ importers:
'@payloadcms/graphql':
specifier: workspace:*
version: link:../packages/graphql
'@payloadcms/kv-redis':
specifier: workspace:*
version: link:../packages/kv-redis
'@payloadcms/live-preview':
specifier: workspace:*
version: link:../packages/live-preview
@@ -3593,6 +3606,9 @@ packages:
cpu: [x64]
os: [win32]
'@ioredis/commands@1.2.0':
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -5907,6 +5923,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -6186,6 +6206,10 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -7305,6 +7329,10 @@ packages:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
ioredis@5.4.1:
resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
engines: {node: '>=12.22.0'}
ip-address@9.0.5:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
@@ -7887,9 +7915,15 @@ packages:
lodash.deburr@4.1.0:
resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==}
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
@@ -8906,6 +8940,14 @@ packages:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
refa@0.12.1:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -9453,6 +9495,9 @@ packages:
resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==}
engines: {node: '>=6'}
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
@@ -12562,6 +12607,8 @@ snapshots:
'@img/sharp-win32-x64@0.33.5':
optional: true
'@ioredis/commands@1.2.0': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -15521,6 +15568,8 @@ snapshots:
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
co@4.6.0: {}
collect-v8-coverage@1.0.2: {}
@@ -15779,6 +15828,8 @@ snapshots:
delayed-stream@1.0.0: {}
denque@2.1.0: {}
dequal@2.0.3: {}
destr@2.0.3: {}
@@ -17110,6 +17161,20 @@ snapshots:
interpret@1.4.0: {}
ioredis@5.4.1:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
debug: 4.3.7
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
redis-errors: 1.2.0
redis-parser: 3.0.0
standard-as-callback: 2.1.0
transitivePeerDependencies:
- supports-color
ip-address@9.0.5:
dependencies:
jsbn: 1.1.0
@@ -17891,8 +17956,12 @@ snapshots:
lodash.deburr@4.1.0: {}
lodash.defaults@4.2.0: {}
lodash.get@4.4.2: {}
lodash.isarguments@3.1.0: {}
lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
@@ -19091,6 +19160,12 @@ snapshots:
dependencies:
resolve: 1.22.8
redis-errors@1.2.0: {}
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
refa@0.12.1:
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -19638,6 +19713,8 @@ snapshots:
dependencies:
type-fest: 0.7.1
standard-as-callback@2.1.0: {}
state-local@1.0.7: {}
std-env@3.7.0: {}

View File

@@ -25,6 +25,8 @@ jest.spyOn(nodemailer, 'createTestAccount').mockImplementation(() => {
})
})
process.env.REDIS_URL = process.env.REDIS_URL ?? 'redis://127.0.0.1:6379'
const dbAdapter = process.env.PAYLOAD_DATABASE || 'mongodb'
generateDatabaseAdapter(dbAdapter)

2
test/kv/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/media
/media-gif

21
test/kv/config.ts Normal file
View File

@@ -0,0 +1,21 @@
import { fileURLToPath } from 'node:url'
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
// ...extend config here
collections: [],
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
},
globals: [],
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})

19
test/kv/eslint.config.js Normal file
View File

@@ -0,0 +1,19 @@
import { rootParserOptions } from '../../eslint.config.js'
import { testEslintConfig } from '../eslint.config.js'
/** @typedef {import('eslint').Linter.Config} Config */
/** @type {Config[]} */
export const index = [
...testEslintConfig,
{
languageOptions: {
parserOptions: {
...rootParserOptions,
tsconfigRootDir: import.meta.dirname,
},
},
},
]
export default index

86
test/kv/int.spec.ts Normal file
View File

@@ -0,0 +1,86 @@
import type { KVAdapterResult, Payload } from 'payload'
import { RedisKVAdapter, redisKVAdapter } from '@payloadcms/kv-redis'
import path from 'path'
import { inMemoryKVAdapter } from 'payload'
import { fileURLToPath } from 'url'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
let payload: Payload
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('KV Adapters', () => {
// --__--__--__--__--__--__--__--__--__
// Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__
beforeAll(async () => {
const initialized = await initPayloadInt(dirname)
;({ payload } = initialized)
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
})
const testKVAdapter = async (adapter?: KVAdapterResult) => {
if (adapter) {
payload.kv = adapter.init({ payload })
}
await payload.kv.set('my-key-1', { userID: 1 })
await payload.kv.set('my-key-2', { userID: 2 })
expect(await payload.kv.get('my-key-1')).toStrictEqual({ userID: 1 })
expect(await payload.kv.get('my-key-2')).toStrictEqual({ userID: 2 })
expect(await payload.kv.get('my-key-3')).toBeNull()
expect(await payload.kv.has('my-key-1')).toBeTruthy()
expect(await payload.kv.has('my-key-2')).toBeTruthy()
expect(await payload.kv.has('my-key-3')).toBeFalsy()
let keys = await payload.kv.keys()
expect(keys).toHaveLength(2)
expect(keys).toContain('my-key-1')
expect(keys).toContain('my-key-2')
await payload.kv.set('my-key-1', { userID: 10 })
expect(await payload.kv.get('my-key-1')).toStrictEqual({ userID: 10 })
await payload.kv.delete('my-key-1')
expect(await payload.kv.get('my-key-1')).toBeNull()
expect(await payload.kv.has('my-key-1')).toBeFalsy()
keys = await payload.kv.keys()
expect(keys).toHaveLength(1)
expect(keys).toContain('my-key-2')
await payload.kv.clear()
expect(await payload.kv.get('my-key-2')).toBeNull()
expect(await payload.kv.has('my-key-2')).toBeFalsy()
keys = await payload.kv.keys()
expect(keys).toHaveLength(0)
if (payload.kv instanceof RedisKVAdapter) {
await payload.kv.redisClient.quit()
}
return true
}
it('databaseKVAdapter', async () => {
// default
expect(await testKVAdapter()).toBeTruthy()
})
it('inMemoryKVAdapter', async () => {
expect(await testKVAdapter(inMemoryKVAdapter())).toBeTruthy()
})
it('redisKVAdapter', async () => {
expect(await testKVAdapter(redisKVAdapter())).toBeTruthy()
})
})

213
test/kv/payload-types.ts Normal file
View File

@@ -0,0 +1,213 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
'payload-kv': PayloadKv;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: null;
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?: {
relationTo: 'users';
value: string | User;
} | null;
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-kv".
*/
export interface PayloadKv {
id: string;
key: string;
data:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-kv_select".
*/
export interface PayloadKvSelect<T extends boolean = true> {
key?: T;
data?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -0,0 +1,13 @@
{
// extend your base config to share compilerOptions, etc
//"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": [
// whatever paths you intend to lint
"./**/*.ts",
"./**/*.tsx"
]
}

3
test/kv/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../tsconfig.json"
}

9
test/kv/types.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import type { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
myObject?: string
// ...
}
}

View File

@@ -33,6 +33,7 @@
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/eslint-plugin": "workspace:*",
"@payloadcms/graphql": "workspace:*",
"@payloadcms/kv-redis": "workspace:*",
"@payloadcms/live-preview": "workspace:*",
"@payloadcms/live-preview-react": "workspace:*",
"@payloadcms/next": "workspace:*",

2
test/realtime/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/media
/media-gif

25
test/realtime/config.ts Normal file
View File

@@ -0,0 +1,25 @@
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { fileURLToPath } from 'node:url'
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
// ...extend config here
collections: [],
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
},
editor: lexicalEditor({}),
globals: [],
onInit: async (payload) => {},
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})

View File

@@ -0,0 +1,19 @@
import { rootParserOptions } from '../../eslint.config.js'
import { testEslintConfig } from '../eslint.config.js'
/** @typedef {import('eslint').Linter.Config} Config */
/** @type {Config[]} */
export const index = [
...testEslintConfig,
{
languageOptions: {
parserOptions: {
...rootParserOptions,
tsconfigRootDir: import.meta.dirname,
},
},
},
]
export default index

44
test/realtime/int.spec.ts Normal file
View File

@@ -0,0 +1,44 @@
import type { Server } from 'http'
import { createServer } from 'http'
import path from 'path'
import { bindWebsocketToServer, type Payload } from 'payload'
import { fileURLToPath } from 'url'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
let payload: Payload
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
let server: Server
describe('_Community Tests', () => {
// --__--__--__--__--__--__--__--__--__
// Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__
beforeAll(async () => {
const initialized = await initPayloadInt(dirname)
;({ payload } = initialized)
server = createServer().listen(3010)
await bindWebsocketToServer({ server, config: payload.config })
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
server.close()
})
it('should work', () => {
expect(true).toBeTruthy()
})
// --__--__--__--__--__--__--__--__--__
// You can run tests against the local API or the REST API
// use the tests below as a guide
// --__--__--__--__--__--__--__--__--__
})

View File

@@ -0,0 +1,339 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
posts: Post;
media: Media;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
posts: PostsSelect<false> | PostsSelect<true>;
media: MediaSelect<false> | MediaSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {
menu: Menu;
};
globalsSelect: {
menu: MenuSelect<false> | MenuSelect<true>;
};
locale: null;
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
*/
export interface Post {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media".
*/
export interface Media {
id: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
thumbnail?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
medium?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
large?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'media';
value: string | Media;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts_select".
*/
export interface PostsSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media_select".
*/
export interface MediaSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
sizes?:
| T
| {
thumbnail?:
| T
| {
url?: T;
width?: T;
height?: T;
mimeType?: T;
filesize?: T;
filename?: T;
};
medium?:
| T
| {
url?: T;
width?: T;
height?: T;
mimeType?: T;
filesize?: T;
filename?: T;
};
large?:
| T
| {
url?: T;
width?: T;
height?: T;
mimeType?: T;
filesize?: T;
filename?: T;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "menu".
*/
export interface Menu {
id: string;
globalText?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "menu_select".
*/
export interface MenuSelect<T extends boolean = true> {
globalText?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -0,0 +1,13 @@
{
// extend your base config to share compilerOptions, etc
//"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": [
// whatever paths you intend to lint
"./**/*.ts",
"./**/*.tsx"
]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "../tsconfig.json"
}

9
test/realtime/types.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import type { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
myObject?: string
// ...
}
}

View File

@@ -19,6 +19,7 @@ export const tgzToPkgNameMap = {
'@payloadcms/graphql': 'payloadcms-graphql-*',
'@payloadcms/live-preview': 'payloadcms-live-preview-*',
'@payloadcms/live-preview-react': 'payloadcms-live-preview-react-*',
'@payloadcms/kv-redis': 'payloadcms-kv-redis-*',
'@payloadcms/next': 'payloadcms-next-*',
'@payloadcms/payload-cloud': 'payloadcms-payload-cloud-*',
'@payloadcms/plugin-cloud-storage': 'payloadcms-plugin-cloud-storage-*',

View File

@@ -127,6 +127,9 @@
{
"path": "./packages/live-preview-vue"
},
{
"path": "./packages/kv-redis"
},
{
"path": "./packages/next"
},