@kiryl.pekarski/payload-plugin-ab 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { S as StorageAdapter } from '../../config-CRUREAW_.js';
1
+ import { S as StorageAdapter } from '../../config-CQrzAj5Q.js';
2
2
  import 'payload';
3
3
 
4
4
  interface PayloadGlobalAdapterConfig {
@@ -1,4 +1,4 @@
1
- import { S as StorageAdapter } from '../../config-CRUREAW_.js';
1
+ import { S as StorageAdapter } from '../../config-CQrzAj5Q.js';
2
2
  import 'payload';
3
3
 
4
4
  interface VercelEdgeAdapterConfig {
@@ -12,15 +12,29 @@ interface StorageAdapter<TVariantData extends object = object> {
12
12
  }
13
13
  interface CollectionABConfig<TVariantData extends object = object> {
14
14
  variantCollectionSlug: string;
15
+ /**
16
+ * Dot-notation path to the pass percentage field on the variant document.
17
+ * Default: 'passPercentage'.
18
+ */
19
+ passPercentageField?: string;
20
+ /**
21
+ * Dot-notation path to the parent field on the variant document.
22
+ * Default: 'page'
23
+ */
24
+ parentField?: string;
25
+ /**
26
+ * Optional: dot-notation path to the tenant field on the variant document.
27
+ */
28
+ tenantField?: string;
15
29
  generatePath: (args: {
16
30
  doc: Record<string, unknown>;
17
- locale: string;
31
+ locale: string | undefined;
18
32
  }) => string | null;
19
33
  /** Generate the data stored per variant in the manifest. */
20
34
  generateVariantData: (args: {
21
35
  doc: Record<string, unknown>;
22
36
  variantDoc: Record<string, unknown>;
23
- locale: string;
37
+ locale: string | undefined;
24
38
  }) => TVariantData;
25
39
  }
26
40
  interface AbTestingPluginConfig<TVariantData extends object = object> {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from 'payload';
2
- import { A as AbTestingPluginConfig } from './config-CRUREAW_.js';
3
- export { C as CollectionABConfig, S as StorageAdapter } from './config-CRUREAW_.js';
2
+ import { A as AbTestingPluginConfig } from './config-CQrzAj5Q.js';
3
+ export { C as CollectionABConfig, S as StorageAdapter } from './config-CQrzAj5Q.js';
4
4
 
5
5
  declare const abTestingPlugin: <TVariantData extends object>(pluginConfig: AbTestingPluginConfig<TVariantData>) => Plugin;
6
6
 
package/dist/index.js CHANGED
@@ -22,9 +22,11 @@ function resolveId(value) {
22
22
 
23
23
  // src/utils/getLocales.ts
24
24
  function getLocales(payload) {
25
- const localization = payload.config.localization;
26
- const locales = localization ? localization.locales ?? [] : [];
27
- return locales.map((l) => typeof l === "string" ? l : l.code ?? String(l));
25
+ const { localization } = payload.config;
26
+ if (!localization) return [void 0];
27
+ const { locales } = localization;
28
+ if (!locales?.length) return [void 0];
29
+ return locales.map((l) => typeof l === "string" ? l : l.code);
28
30
  }
29
31
 
30
32
  // src/hooks/buildAfterChangeHook.ts
@@ -142,6 +144,93 @@ function buildAfterDeleteHook(parentCollectionSlug, abConfig, pluginConfig) {
142
144
  };
143
145
  }
144
146
 
147
+ // src/hooks/validateVariantPercentageSum.ts
148
+ import { ValidationError } from "payload";
149
+ function getNestedValue(obj, path) {
150
+ return path.split(".").reduce((acc, key) => {
151
+ if (acc !== null && typeof acc === "object") {
152
+ return acc[key];
153
+ }
154
+ return void 0;
155
+ }, obj);
156
+ }
157
+ function createValidateVariantPercentageSum(config) {
158
+ const { variantCollectionSlug, parentField, passPercentageField, tenantField } = config;
159
+ return async ({ data, originalDoc, req, operation }) => {
160
+ const passPercentage = getNestedValue(data, passPercentageField);
161
+ if (passPercentage === void 0 || passPercentage === null) return data;
162
+ const parentId = resolveId(getNestedValue(data, parentField));
163
+ if (!parentId) return data;
164
+ const andConditions = [{ [parentField]: { equals: parentId } }];
165
+ if (tenantField) {
166
+ const tenantId = resolveId(getNestedValue(data, tenantField));
167
+ if (tenantId) {
168
+ andConditions.push({ [tenantField]: { equals: tenantId } });
169
+ }
170
+ }
171
+ if (operation === "update" && originalDoc?.id) {
172
+ andConditions.push({ id: { not_equals: originalDoc.id } });
173
+ }
174
+ const { docs: siblings } = await req.payload.find({
175
+ collection: variantCollectionSlug,
176
+ where: { and: andConditions },
177
+ depth: 0,
178
+ req
179
+ });
180
+ const existingSum = siblings.reduce((sum, doc) => {
181
+ const percentage = getNestedValue(doc, passPercentageField);
182
+ return sum + (typeof percentage === "number" ? percentage : 0);
183
+ }, 0);
184
+ if (existingSum + passPercentage > 100) {
185
+ const remaining = 100 - existingSum;
186
+ throw new ValidationError({
187
+ errors: [
188
+ {
189
+ path: passPercentageField,
190
+ message: `Total variant traffic for this page is ${existingSum}%. This variant cannot exceed ${remaining}% (would exceed 100%).`
191
+ }
192
+ ]
193
+ });
194
+ }
195
+ return data;
196
+ };
197
+ }
198
+
199
+ // src/utils/isFieldPathExists.ts
200
+ function isFieldPathExists(fields, path) {
201
+ const [head, ...rest] = path.split(".");
202
+ for (const field of fields) {
203
+ if ("name" in field && field.name === head) {
204
+ if (rest.length === 0) return true;
205
+ if ("fields" in field && Array.isArray(field.fields)) {
206
+ return isFieldPathExists(field.fields, rest.join("."));
207
+ }
208
+ return false;
209
+ }
210
+ if (field.type === "tabs") {
211
+ const { tabs } = field;
212
+ for (const tab of tabs) {
213
+ if (tab.name) {
214
+ if (tab.name === head) {
215
+ if (rest.length === 0) return true;
216
+ if (isFieldPathExists(tab.fields, rest.join("."))) return true;
217
+ }
218
+ } else {
219
+ if (isFieldPathExists(tab.fields, path)) return true;
220
+ }
221
+ }
222
+ }
223
+ if ((field.type === "row" || field.type === "collapsible") && "fields" in field && Array.isArray(field.fields)) {
224
+ if (isFieldPathExists(field.fields, path)) return true;
225
+ }
226
+ }
227
+ return false;
228
+ }
229
+
230
+ // src/constants.ts
231
+ var DEFAULT_PASS_PERCENTAGE_FIELD = "passPercentage";
232
+ var DEFAULT_PARENT_FIELD = "page";
233
+
145
234
  // src/utils/addHooksToVariantCollections.ts
146
235
  function addHooksToVariantCollections(config, pluginConfig, variantToParent) {
147
236
  const patchedCollections = (config.collections ?? []).map((collection) => {
@@ -149,10 +238,27 @@ function addHooksToVariantCollections(config, pluginConfig, variantToParent) {
149
238
  if (!parentSlug) return collection;
150
239
  const abConfig = pluginConfig.collections[parentSlug];
151
240
  if (!abConfig) return collection;
241
+ let resolvedPassPercentageField = null;
242
+ const passPercentagePath = typeof abConfig.passPercentageField === "string" ? abConfig.passPercentageField : DEFAULT_PASS_PERCENTAGE_FIELD;
243
+ if (isFieldPathExists(collection.fields, passPercentagePath)) {
244
+ resolvedPassPercentageField = passPercentagePath;
245
+ }
246
+ const beforeChangeHooks = [...collection.hooks?.beforeChange ?? []];
247
+ if (resolvedPassPercentageField) {
248
+ beforeChangeHooks.push(
249
+ createValidateVariantPercentageSum({
250
+ variantCollectionSlug: abConfig.variantCollectionSlug,
251
+ parentField: abConfig.parentField ?? DEFAULT_PARENT_FIELD,
252
+ passPercentageField: resolvedPassPercentageField,
253
+ tenantField: abConfig.tenantField
254
+ })
255
+ );
256
+ }
152
257
  return {
153
258
  ...collection,
154
259
  hooks: {
155
260
  ...collection.hooks,
261
+ beforeChange: beforeChangeHooks,
156
262
  afterChange: [
157
263
  ...collection.hooks?.afterChange ?? [],
158
264
  buildAfterChangeHook(parentSlug, abConfig, pluginConfig)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/buildVariantToParentCollectionSlugsMap.ts","../src/utils/resolveId.ts","../src/utils/getLocales.ts","../src/hooks/buildAfterChangeHook.ts","../src/hooks/buildAfterDeleteHook.ts","../src/utils/addHooksToVariantCollections.ts","../src/plugin.ts"],"sourcesContent":["import type { CollectionABConfig } from \"../types/config\";\n\nexport function buildVariantToParentCollectionSlugsMap<TVariantData extends object>(\n collections: Record<string, CollectionABConfig<TVariantData>>,\n) {\n const variantToParent = new Map<string, string>();\n\n for (const [parentSlug, abConfig] of Object.entries(collections)) {\n if (abConfig) variantToParent.set(abConfig.variantCollectionSlug, parentSlug);\n }\n\n return variantToParent;\n}\n","interface BaseDocument {\n id: number | string;\n}\n\nconst isValueIsBaseDocument = (value: object): value is BaseDocument => {\n return \"id\" in value;\n};\n\nexport function resolveId(value: unknown) {\n if (!value) return null;\n\n if (typeof value === \"number\" || typeof value === \"string\") return value;\n\n if (typeof value === \"object\" && isValueIsBaseDocument(value)) {\n return value.id;\n }\n\n return null;\n}\n","import type { Payload } from \"payload\";\n\nexport function getLocales(payload: Payload): string[] {\n const localization = payload.config.localization;\n\n const locales = localization ? (localization.locales ?? []) : [];\n\n return locales.map((l) => (typeof l === \"string\" ? l : (l.code ?? String(l))));\n}\n","import type { CollectionAfterChangeHook, CollectionSlug, TypedLocale } from \"payload\";\nimport type { AbTestingPluginConfig, CollectionABConfig } from \"../types/config\";\nimport { resolveId } from \"../utils/resolveId\";\nimport { getLocales } from \"../utils/getLocales\";\n\nexport function buildAfterChangeHook<TVariantData extends object>(\n parentCollectionSlug: string,\n abConfig: CollectionABConfig<TVariantData>,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n): CollectionAfterChangeHook {\n return async ({ doc, req, previousDoc }) => {\n const { payload } = req;\n if (!payload) return;\n\n const pageId = resolveId(doc.page);\n if (!pageId) return;\n\n const locales = getLocales(payload);\n\n const previousPageId = previousDoc ? resolveId(previousDoc.page) : null;\n if (previousPageId && previousPageId !== pageId) {\n for (const locale of locales) {\n const oldPageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: previousPageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!oldPageDoc) continue;\n\n const oldManifestKey = abConfig.generatePath({ doc: oldPageDoc, locale });\n if (!oldManifestKey) continue;\n\n const remainingOldVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: {\n and: [{ page: { equals: previousPageId } }, { id: { not_equals: doc.id } }],\n },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n if (remainingOldVariants.docs.length === 0) {\n await pluginConfig.storage.clear(oldManifestKey, payload);\n } else {\n const oldVariantData = remainingOldVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: oldPageDoc, variantDoc, locale }),\n );\n await pluginConfig.storage.write(oldManifestKey, oldVariantData, payload);\n }\n }\n }\n\n for (const locale of locales) {\n const pageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: pageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!pageDoc) continue;\n\n const manifestKey = abConfig.generatePath({ doc: pageDoc, locale });\n if (!manifestKey) continue;\n\n const allVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: { page: { equals: pageId } },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n const variantData = allVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: pageDoc, variantDoc, locale }),\n );\n\n await pluginConfig.storage.write(manifestKey, variantData, payload);\n }\n };\n}\n","import type { CollectionAfterDeleteHook, CollectionSlug, TypedLocale } from \"payload\";\nimport type { AbTestingPluginConfig, CollectionABConfig } from \"../types/config\";\nimport { resolveId } from \"../utils/resolveId\";\nimport { getLocales } from \"../utils/getLocales\";\n\nexport function buildAfterDeleteHook<TVariantData extends object>(\n parentCollectionSlug: string,\n abConfig: CollectionABConfig<TVariantData>,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n): CollectionAfterDeleteHook {\n return async ({ doc, req, id }) => {\n const { payload } = req;\n if (!payload) return;\n\n const pageId = resolveId(doc.page);\n if (!pageId) return;\n\n const locales = getLocales(payload);\n\n for (const locale of locales) {\n const pageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: pageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!pageDoc) continue;\n\n const manifestKey = abConfig.generatePath({ doc: pageDoc, locale });\n if (!manifestKey) continue;\n\n const remainingVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: {\n and: [{ page: { equals: pageId } }, { id: { not_equals: id } }],\n },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n if (remainingVariants.docs.length === 0) {\n await pluginConfig.storage.clear(manifestKey, payload);\n } else {\n const variantData = remainingVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: pageDoc, variantDoc, locale }),\n );\n\n await pluginConfig.storage.write(manifestKey, variantData, payload);\n }\n }\n };\n}\n","import type { CollectionConfig, Config } from \"payload\";\nimport type { AbTestingPluginConfig } from \"../types/config\";\nimport { buildAfterChangeHook } from \"../hooks/buildAfterChangeHook\";\nimport { buildAfterDeleteHook } from \"../hooks/buildAfterDeleteHook\";\n\nexport function addHooksToVariantCollections<TVariantData extends object>(\n config: Config,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n variantToParent: Map<string, string>,\n) {\n const patchedCollections = (config.collections ?? []).map((collection) => {\n const parentSlug = variantToParent.get(collection.slug);\n if (!parentSlug) return collection;\n\n const abConfig = pluginConfig.collections[parentSlug];\n if (!abConfig) return collection;\n\n return {\n ...collection,\n hooks: {\n ...collection.hooks,\n afterChange: [\n ...(collection.hooks?.afterChange ?? []),\n buildAfterChangeHook(parentSlug, abConfig, pluginConfig),\n ],\n afterDelete: [\n ...(collection.hooks?.afterDelete ?? []),\n buildAfterDeleteHook(parentSlug, abConfig, pluginConfig),\n ],\n },\n } as CollectionConfig;\n });\n\n return patchedCollections;\n}\n","import type { Config, Plugin } from \"payload\";\nimport type { AbTestingPluginConfig } from \"./types/config\";\nimport { buildVariantToParentCollectionSlugsMap } from \"./utils/buildVariantToParentCollectionSlugsMap\";\nimport { addHooksToVariantCollections } from \"./utils/addHooksToVariantCollections\";\n\nexport const abTestingPlugin =\n <TVariantData extends object>(pluginConfig: AbTestingPluginConfig<TVariantData>): Plugin =>\n (incomingConfig: Config): Config => {\n const { enabled = true, debug = false, collections, storage } = pluginConfig;\n\n if (!enabled) return incomingConfig;\n\n const extraGlobals = storage.createGlobal ? [storage.createGlobal(debug)] : [];\n\n const variantToParent = buildVariantToParentCollectionSlugsMap<TVariantData>(collections);\n\n const patchedCollections = addHooksToVariantCollections<TVariantData>(\n incomingConfig,\n pluginConfig,\n variantToParent,\n );\n\n return {\n ...incomingConfig,\n collections: patchedCollections,\n globals: [...(incomingConfig.globals ?? []), ...extraGlobals],\n };\n };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/buildVariantToParentCollectionSlugsMap.ts","../src/utils/resolveId.ts","../src/utils/getLocales.ts","../src/hooks/buildAfterChangeHook.ts","../src/hooks/buildAfterDeleteHook.ts","../src/hooks/validateVariantPercentageSum.ts","../src/utils/isFieldPathExists.ts","../src/constants.ts","../src/utils/addHooksToVariantCollections.ts","../src/plugin.ts"],"sourcesContent":["import type { CollectionABConfig } from \"../types/config\";\n\nexport function buildVariantToParentCollectionSlugsMap<TVariantData extends object>(\n collections: Record<string, CollectionABConfig<TVariantData>>,\n) {\n const variantToParent = new Map<string, string>();\n\n for (const [parentSlug, abConfig] of Object.entries(collections)) {\n if (abConfig) variantToParent.set(abConfig.variantCollectionSlug, parentSlug);\n }\n\n return variantToParent;\n}\n","interface BaseDocument {\n id: number | string;\n}\n\nconst isValueIsBaseDocument = (value: object): value is BaseDocument => {\n return \"id\" in value;\n};\n\nexport function resolveId(value: unknown) {\n if (!value) return null;\n\n if (typeof value === \"number\" || typeof value === \"string\") return value;\n\n if (typeof value === \"object\" && isValueIsBaseDocument(value)) {\n return value.id;\n }\n\n return null;\n}\n","import type { Payload } from \"payload\";\n\nexport function getLocales(payload: Payload): (string | undefined)[] {\n const { localization } = payload.config;\n\n if (!localization) return [undefined];\n\n const { locales } = localization;\n\n if (!locales?.length) return [undefined];\n\n return locales.map((l) => (typeof l === \"string\" ? l : l.code));\n}\n","import type { CollectionAfterChangeHook, CollectionSlug, TypedLocale } from \"payload\";\nimport type { AbTestingPluginConfig, CollectionABConfig } from \"../types/config\";\nimport { resolveId } from \"../utils/resolveId\";\nimport { getLocales } from \"../utils/getLocales\";\n\nexport function buildAfterChangeHook<TVariantData extends object>(\n parentCollectionSlug: string,\n abConfig: CollectionABConfig<TVariantData>,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n): CollectionAfterChangeHook {\n return async ({ doc, req, previousDoc }) => {\n const { payload } = req;\n if (!payload) return;\n\n const pageId = resolveId(doc.page);\n if (!pageId) return;\n\n const locales = getLocales(payload);\n\n const previousPageId = previousDoc ? resolveId(previousDoc.page) : null;\n if (previousPageId && previousPageId !== pageId) {\n for (const locale of locales) {\n const oldPageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: previousPageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!oldPageDoc) continue;\n\n const oldManifestKey = abConfig.generatePath({ doc: oldPageDoc, locale });\n if (!oldManifestKey) continue;\n\n const remainingOldVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: {\n and: [{ page: { equals: previousPageId } }, { id: { not_equals: doc.id } }],\n },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n if (remainingOldVariants.docs.length === 0) {\n await pluginConfig.storage.clear(oldManifestKey, payload);\n } else {\n const oldVariantData = remainingOldVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: oldPageDoc, variantDoc, locale }),\n );\n await pluginConfig.storage.write(oldManifestKey, oldVariantData, payload);\n }\n }\n }\n\n for (const locale of locales) {\n const pageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: pageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!pageDoc) continue;\n\n const manifestKey = abConfig.generatePath({ doc: pageDoc, locale });\n if (!manifestKey) continue;\n\n const allVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: { page: { equals: pageId } },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n const variantData = allVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: pageDoc, variantDoc, locale }),\n );\n\n await pluginConfig.storage.write(manifestKey, variantData, payload);\n }\n };\n}\n","import type { CollectionAfterDeleteHook, CollectionSlug, TypedLocale } from \"payload\";\nimport type { AbTestingPluginConfig, CollectionABConfig } from \"../types/config\";\nimport { resolveId } from \"../utils/resolveId\";\nimport { getLocales } from \"../utils/getLocales\";\n\nexport function buildAfterDeleteHook<TVariantData extends object>(\n parentCollectionSlug: string,\n abConfig: CollectionABConfig<TVariantData>,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n): CollectionAfterDeleteHook {\n return async ({ doc, req, id }) => {\n const { payload } = req;\n if (!payload) return;\n\n const pageId = resolveId(doc.page);\n if (!pageId) return;\n\n const locales = getLocales(payload);\n\n for (const locale of locales) {\n const pageDoc = await payload.findByID({\n collection: parentCollectionSlug as CollectionSlug,\n id: pageId,\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n req,\n });\n if (!pageDoc) continue;\n\n const manifestKey = abConfig.generatePath({ doc: pageDoc, locale });\n if (!manifestKey) continue;\n\n const remainingVariants = await payload.find({\n collection: abConfig.variantCollectionSlug as CollectionSlug,\n where: {\n and: [{ page: { equals: pageId } }, { id: { not_equals: id } }],\n },\n depth: 2,\n locale: locale as TypedLocale,\n overrideAccess: true,\n limit: 100,\n req,\n });\n\n if (remainingVariants.docs.length === 0) {\n await pluginConfig.storage.clear(manifestKey, payload);\n } else {\n const variantData = remainingVariants.docs.map((variantDoc) =>\n abConfig.generateVariantData({ doc: pageDoc, variantDoc, locale }),\n );\n\n await pluginConfig.storage.write(manifestKey, variantData, payload);\n }\n }\n };\n}\n","import { ValidationError } from \"payload\";\nimport type { CollectionBeforeChangeHook, CollectionSlug, Where } from \"payload\";\nimport { resolveId } from \"../utils/resolveId\";\n\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n return path.split(\".\").reduce((acc: unknown, key) => {\n if (acc !== null && typeof acc === \"object\") {\n return (acc as Record<string, unknown>)[key];\n }\n\n return undefined;\n }, obj);\n}\n\ninterface ValidateVariantPercentageSumConfig {\n variantCollectionSlug: CollectionSlug;\n parentField: string;\n passPercentageField: string;\n tenantField?: string;\n}\n\nexport function createValidateVariantPercentageSum(\n config: ValidateVariantPercentageSumConfig,\n): CollectionBeforeChangeHook {\n const { variantCollectionSlug, parentField, passPercentageField, tenantField } = config;\n\n return async ({ data, originalDoc, req, operation }) => {\n const passPercentage = getNestedValue(data, passPercentageField);\n if (passPercentage === undefined || passPercentage === null) return data;\n\n const parentId = resolveId(getNestedValue(data, parentField));\n if (!parentId) return data;\n\n const andConditions: Where[] = [{ [parentField]: { equals: parentId } }];\n\n if (tenantField) {\n const tenantId = resolveId(getNestedValue(data, tenantField));\n if (tenantId) {\n andConditions.push({ [tenantField]: { equals: tenantId } });\n }\n }\n\n if (operation === \"update\" && originalDoc?.id) {\n andConditions.push({ id: { not_equals: originalDoc.id } });\n }\n\n const { docs: siblings } = await req.payload.find({\n collection: variantCollectionSlug,\n where: { and: andConditions },\n depth: 0,\n req,\n });\n\n const existingSum = siblings.reduce((sum, doc) => {\n const percentage = getNestedValue(doc, passPercentageField);\n\n return sum + (typeof percentage === \"number\" ? percentage : 0);\n }, 0);\n\n if (existingSum + (passPercentage as number) > 100) {\n const remaining = 100 - existingSum;\n throw new ValidationError({\n errors: [\n {\n path: passPercentageField,\n message: `Total variant traffic for this page is ${existingSum}%. This variant cannot exceed ${remaining}% (would exceed 100%).`,\n },\n ],\n });\n }\n\n return data;\n };\n}\n","import type { Field } from \"payload\";\n\ntype TabLike = { name?: string; fields: Field[] };\n\ntype TabsFieldLike = { type: \"tabs\"; tabs: TabLike[] };\n\ntype FieldsContainer = { fields: Field[] };\n\nexport function isFieldPathExists(fields: Field[], path: string): boolean {\n const [head, ...rest] = path.split(\".\");\n\n for (const field of fields) {\n // Named fields\n if (\"name\" in field && field.name === head) {\n if (rest.length === 0) return true;\n\n if (\"fields\" in field && Array.isArray((field as unknown as FieldsContainer).fields)) {\n return isFieldPathExists((field as unknown as FieldsContainer).fields, rest.join(\".\"));\n }\n\n return false;\n }\n\n // Tabs field\n if (field.type === \"tabs\") {\n const { tabs } = field as unknown as TabsFieldLike;\n\n for (const tab of tabs) {\n if (tab.name) {\n if (tab.name === head) {\n if (rest.length === 0) return true;\n\n if (isFieldPathExists(tab.fields, rest.join(\".\"))) return true;\n }\n } else {\n if (isFieldPathExists(tab.fields, path)) return true;\n }\n }\n }\n\n // Transparent layout fields\n if (\n (field.type === \"row\" || field.type === \"collapsible\")\n && \"fields\" in field\n && Array.isArray((field as unknown as FieldsContainer).fields)\n ) {\n if (isFieldPathExists((field as unknown as FieldsContainer).fields, path)) return true;\n }\n }\n\n return false;\n}\n","export const DEFAULT_PASS_PERCENTAGE_FIELD = \"passPercentage\";\nexport const DEFAULT_PARENT_FIELD = \"page\";\n","import type { CollectionConfig, CollectionSlug, Config } from \"payload\";\nimport type { AbTestingPluginConfig } from \"../types/config\";\nimport { buildAfterChangeHook } from \"../hooks/buildAfterChangeHook\";\nimport { buildAfterDeleteHook } from \"../hooks/buildAfterDeleteHook\";\nimport { createValidateVariantPercentageSum } from \"../hooks/validateVariantPercentageSum\";\nimport { isFieldPathExists } from \"./isFieldPathExists\";\nimport { DEFAULT_PARENT_FIELD, DEFAULT_PASS_PERCENTAGE_FIELD } from \"../constants\";\n\nexport function addHooksToVariantCollections<TVariantData extends object>(\n config: Config,\n pluginConfig: AbTestingPluginConfig<TVariantData>,\n variantToParent: Map<string, string>,\n) {\n const patchedCollections = (config.collections ?? []).map((collection) => {\n const parentSlug = variantToParent.get(collection.slug);\n if (!parentSlug) return collection;\n\n const abConfig = pluginConfig.collections[parentSlug];\n if (!abConfig) return collection;\n\n let resolvedPassPercentageField: string | null = null;\n\n const passPercentagePath =\n typeof abConfig.passPercentageField === \"string\" ? abConfig.passPercentageField : DEFAULT_PASS_PERCENTAGE_FIELD;\n\n if (isFieldPathExists(collection.fields, passPercentagePath)) {\n resolvedPassPercentageField = passPercentagePath;\n }\n\n const beforeChangeHooks = [...(collection.hooks?.beforeChange ?? [])];\n\n if (resolvedPassPercentageField) {\n beforeChangeHooks.push(\n createValidateVariantPercentageSum({\n variantCollectionSlug: abConfig.variantCollectionSlug as CollectionSlug,\n parentField: abConfig.parentField ?? DEFAULT_PARENT_FIELD,\n passPercentageField: resolvedPassPercentageField,\n tenantField: abConfig.tenantField,\n }),\n );\n }\n\n return {\n ...collection,\n hooks: {\n ...collection.hooks,\n beforeChange: beforeChangeHooks,\n afterChange: [\n ...(collection.hooks?.afterChange ?? []),\n buildAfterChangeHook(parentSlug, abConfig, pluginConfig),\n ],\n afterDelete: [\n ...(collection.hooks?.afterDelete ?? []),\n buildAfterDeleteHook(parentSlug, abConfig, pluginConfig),\n ],\n },\n } as CollectionConfig;\n });\n\n return patchedCollections;\n}\n","import type { Config, Plugin } from \"payload\";\nimport type { AbTestingPluginConfig } from \"./types/config\";\nimport { buildVariantToParentCollectionSlugsMap } from \"./utils/buildVariantToParentCollectionSlugsMap\";\nimport { addHooksToVariantCollections } from \"./utils/addHooksToVariantCollections\";\n\nexport const abTestingPlugin =\n <TVariantData extends object>(pluginConfig: AbTestingPluginConfig<TVariantData>): Plugin =>\n (incomingConfig: Config): Config => {\n const { enabled = true, debug = false, collections, storage } = pluginConfig;\n\n if (!enabled) return incomingConfig;\n\n const extraGlobals = storage.createGlobal ? [storage.createGlobal(debug)] : [];\n\n const variantToParent = buildVariantToParentCollectionSlugsMap<TVariantData>(collections);\n\n const patchedCollections = addHooksToVariantCollections<TVariantData>(\n incomingConfig,\n pluginConfig,\n variantToParent,\n );\n\n return {\n ...incomingConfig,\n collections: patchedCollections,\n globals: [...(incomingConfig.globals ?? []), ...extraGlobals],\n };\n };\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,79 +1,79 @@
1
- {
2
- "name": "@kiryl.pekarski/payload-plugin-ab",
3
- "version": "1.0.1",
4
- "description": "A/B testing plugin for Payload CMS",
5
- "keywords": [
6
- "payload-cms",
7
- "payload-plugin",
8
- "ab-testing",
9
- "split-testing",
10
- "a-b-testing",
11
- "page-variants",
12
- "next.js",
13
- "vercel-edge",
14
- "edge-config",
15
- "middleware",
16
- "typescript",
17
- "cms",
18
- "conversion-optimization"
19
- ],
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/focusreactive/payload-plugin-ab"
24
- },
25
- "author": "Kiryl Pekarski <kiryl.pekarski@fr.team>",
26
- "type": "module",
27
- "main": "./dist/index.js",
28
- "types": "./dist/index.d.ts",
29
- "files": [
30
- "dist"
31
- ],
32
- "typesVersions": {
33
- "*": {
34
- "adapters/payload-global": [
35
- "./dist/adapters/payloadGlobal/index.d.ts"
36
- ],
37
- "adapters/vercel-edge": [
38
- "./dist/adapters/vercelEdge/index.d.ts"
39
- ]
40
- }
41
- },
42
- "exports": {
43
- ".": {
44
- "import": "./dist/index.js",
45
- "types": "./dist/index.d.ts"
46
- },
47
- "./adapters/payload-global": {
48
- "import": "./dist/adapters/payloadGlobal/index.js",
49
- "types": "./dist/adapters/payloadGlobal/index.d.ts"
50
- },
51
- "./adapters/vercel-edge": {
52
- "import": "./dist/adapters/vercelEdge/index.js",
53
- "types": "./dist/adapters/vercelEdge/index.d.ts"
54
- }
55
- },
56
- "scripts": {
57
- "build": "tsup",
58
- "lint": "eslint src/",
59
- "lint:fix": "eslint src/ --fix",
60
- "format": "prettier --write src/",
61
- "format:check": "prettier --check src/"
62
- },
63
- "peerDependencies": {
64
- "payload": "^3.0.0"
65
- },
66
- "optionalDependencies": {
67
- "@vercel/edge-config": "^1.0.0"
68
- },
69
- "devDependencies": {
70
- "@types/node": "^20.0.0",
71
- "eslint": "^9.0.0",
72
- "eslint-config-prettier": "^9.0.0",
73
- "payload": "^3.73.0",
74
- "prettier": "^3.0.0",
75
- "tsup": "^8.0.0",
76
- "typescript": "^5.0.0",
77
- "typescript-eslint": "^8.0.0"
78
- }
79
- }
1
+ {
2
+ "name": "@kiryl.pekarski/payload-plugin-ab",
3
+ "version": "1.1.0",
4
+ "description": "A/B testing plugin for Payload CMS",
5
+ "keywords": [
6
+ "payload-cms",
7
+ "payload-plugin",
8
+ "ab-testing",
9
+ "split-testing",
10
+ "a-b-testing",
11
+ "page-variants",
12
+ "next.js",
13
+ "vercel-edge",
14
+ "edge-config",
15
+ "middleware",
16
+ "typescript",
17
+ "cms",
18
+ "conversion-optimization"
19
+ ],
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/focusreactive/payload-plugin-ab"
24
+ },
25
+ "author": "Kiryl Pekarski <kiryl.pekarski@fr.team>",
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "typesVersions": {
33
+ "*": {
34
+ "adapters/payload-global": [
35
+ "./dist/adapters/payloadGlobal/index.d.ts"
36
+ ],
37
+ "adapters/vercel-edge": [
38
+ "./dist/adapters/vercelEdge/index.d.ts"
39
+ ]
40
+ }
41
+ },
42
+ "exports": {
43
+ ".": {
44
+ "import": "./dist/index.js",
45
+ "types": "./dist/index.d.ts"
46
+ },
47
+ "./adapters/payload-global": {
48
+ "import": "./dist/adapters/payloadGlobal/index.js",
49
+ "types": "./dist/adapters/payloadGlobal/index.d.ts"
50
+ },
51
+ "./adapters/vercel-edge": {
52
+ "import": "./dist/adapters/vercelEdge/index.js",
53
+ "types": "./dist/adapters/vercelEdge/index.d.ts"
54
+ }
55
+ },
56
+ "peerDependencies": {
57
+ "payload": "^3.0.0"
58
+ },
59
+ "optionalDependencies": {
60
+ "@vercel/edge-config": "^1.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^20.0.0",
64
+ "eslint": "^9.0.0",
65
+ "eslint-config-prettier": "^9.0.0",
66
+ "payload": "^3.73.0",
67
+ "prettier": "^3.0.0",
68
+ "tsup": "^8.0.0",
69
+ "typescript": "^5.0.0",
70
+ "typescript-eslint": "^8.0.0"
71
+ },
72
+ "scripts": {
73
+ "build": "tsup",
74
+ "lint": "eslint src/",
75
+ "lint:fix": "eslint src/ --fix",
76
+ "format": "prettier --write src/",
77
+ "format:check": "prettier --check src/"
78
+ }
79
+ }