UNPKG

@ninetailed/experience.js-utils-contentful

Version:
276 lines (266 loc) 7.75 kB
import { z } from 'zod'; import { logger } from '@ninetailed/experience.js-shared'; import { Config, ExperienceMapper as ExperienceMapper$1 } from '@ninetailed/experience.js-utils'; /** * This does not work anymore from zod > 3.21.0: * * We have to cast the result of passthrough() to z.ZodObject<{}> to make it work, * as the inferred type changed to {} & { [k: string]: unknown; } * It was {} before, so we do the type cast to get it back to {}. */ // eslint-disable-next-line @typescript-eslint/ban-types const EntryFields = z.object({}).passthrough(); const EntryLink = z.object({ type: z.string().optional(), linkType: z.string().optional(), id: z.string() }); const LinkedEntity = z.object({ sys: EntryLink }); const EntrySchema = z.object({ sys: z.object({ type: z.string().optional(), id: z.string(), createdAt: z.string().optional(), updatedAt: z.string().optional(), locale: z.string().optional(), revision: z.number().optional(), space: LinkedEntity.optional(), environment: LinkedEntity.optional(), contentType: LinkedEntity.optional() }), fields: EntryFields, metadata: z.object({ tags: z.array(z.object({ sys: EntryLink.extend({ linkType: z.string() }) })) }).optional() }); const parse$2 = input => { const output = EntrySchema.parse(input); return Object.assign({}, output, { fields: input.fields }); }; const safeParse$2 = input => { const output = EntrySchema.safeParse(input); if (!output.success) { return output; } return Object.assign({}, output, { data: Object.assign({}, output.data, { fields: input.fields }) }); }; const Entry = Object.assign({}, EntrySchema, { parse: parse$2, safeParse: safeParse$2 }); const AudienceEntryFields = EntryFields.extend({ /** * The name of the audience (Short Text) */ nt_name: z.string(), /** * The description of the audience (Short Text) */ nt_description: z.string().optional(), /** * The internal id of the audience (Short Text) */ nt_audience_id: z.string() }); const AudienceEntry = EntrySchema.extend({ fields: AudienceEntryFields }); class AudienceMapper { static mapAudience(audience) { return { id: audience.fields.nt_audience_id, name: audience.fields.nt_name || '', description: audience.fields.nt_description || '' }; } } AudienceMapper.isAudienceEntry = entry => { return AudienceEntry.safeParse(entry).success; }; const ExperienceEntryFields = z.object({ /** * The name of the experience (Short Text) */ nt_name: z.string(), /** * The description of the experience (Short Text) */ nt_description: z.string().optional().nullable(), /** * The type if the experience (nt_experiment | nt_personalization) */ nt_type: z.union([z.string(), z.string()]), /** * The config of the experience (JSON) */ nt_config: Config.optional().nullable().default(null).transform(val => { return val != null ? val : { traffic: 0, distribution: [0.5, 0.5], components: [], sticky: false }; }), /** * The audience of the experience (Audience) */ nt_audience: AudienceEntry.optional().nullable(), /** * All used variants of the experience (Contentful references to other Content Types) */ nt_variants: z.array(EntrySchema).optional() }); const ExperienceEntrySchema = EntrySchema.extend({ fields: ExperienceEntryFields }); const parse$1 = input => { const output = ExperienceEntrySchema.parse(input); return Object.assign({}, output, { fields: Object.assign({}, output.fields, { nt_variants: input.fields.nt_variants || [] }) }); }; const safeParse$1 = input => { const output = ExperienceEntrySchema.safeParse(input); if (!output.success) { return output; } return Object.assign({}, output, { data: Object.assign({}, output.data, { fields: Object.assign({}, output.data.fields, { nt_variants: input.fields.nt_variants || [] }) }) }); }; const ExperienceEntry = Object.assign({}, ExperienceEntrySchema, { parse: parse$1, safeParse: safeParse$1 }); const ExperimentEntrySchema = ExperienceEntrySchema.extend({ fields: ExperienceEntryFields.extend({ nt_type: z.string().regex(/^nt_experiment$/g) }) }); const parse = input => { const output = ExperimentEntrySchema.parse(input); return Object.assign({}, output, { fields: Object.assign({}, output.fields, { nt_variants: input.fields.nt_variants || [] }) }); }; const safeParse = input => { const output = ExperimentEntrySchema.safeParse(input); if (!output.success) { return output; } return Object.assign({}, output, { data: Object.assign({}, output.data, { fields: Object.assign({}, output.data.fields, { nt_variants: input.fields.nt_variants || [] }) }) }); }; const ExperimentEntry = Object.assign({}, ExperimentEntrySchema, { parse, safeParse }); function mapAudience(ctfAudienceEntry) { return { id: ctfAudienceEntry.fields.nt_audience_id, name: ctfAudienceEntry.fields.nt_name, description: ctfAudienceEntry.fields.nt_description || '' }; } function createExperience(id, fields, variants) { const { nt_name, nt_description, nt_type, nt_audience, nt_config } = fields; return Object.assign({ id, name: nt_name, description: nt_description || '', type: nt_type }, nt_audience ? { audience: mapAudience(nt_audience) } : {}, { config: nt_config, variants }); } function validateExperienceEntry(entry) { const parsedExperience = ExperienceEntry.safeParse(entry); if (!parsedExperience.success) { logger.warn('[Ninetailed Contentful ExperienceMapper]', 'Error parsing experience', parsedExperience.error.format()); throw new Error(`[Ninetailed Contentful ExperienceMapper] The Experience Input is not valid. Please filter data first with "ExperienceMapper.isExperienceEntry".\n${JSON.stringify(parsedExperience.error.format(), null, 2)}`); } return parsedExperience.data; } class ExperienceMapper { static isExperienceEntry(entry) { return ExperienceEntry.safeParse(entry).success; } static mapExperience(ctfEntry) { const { sys, fields } = validateExperienceEntry(ctfEntry); const variants = fields.nt_variants.map(variant => Object.assign({}, variant, { id: variant.sys.id })); const experience = createExperience(sys.id, fields, variants); return ExperienceMapper$1.mapExperience(experience); } static mapCustomExperience(ctfEntry, mapFn) { const { sys, fields } = validateExperienceEntry(ctfEntry); const variants = fields.nt_variants.map(mapFn); const experience = createExperience(sys.id, fields, variants); return ExperienceMapper$1.mapExperience(experience); } static async mapCustomExperienceAsync(ctfEntry, mapFn) { const { sys, fields } = validateExperienceEntry(ctfEntry); const variants = await Promise.all(fields.nt_variants.map(mapFn)); const experience = createExperience(sys.id, fields, variants); return ExperienceMapper$1.mapExperience(experience); } static isExperiment(entry) { return ExperimentEntry.safeParse(entry).success; } static mapExperiment(entry) { return ExperienceMapper.mapCustomExperience(entry, () => ({ id: '' })); } static mapBaselineWithExperiences(entry) { return entry.fields.nt_experiences.filter(ExperienceMapper.isExperienceEntry).map(experience => ExperienceMapper.mapExperience(experience)); } } const isEntry = entry => { return Entry.safeParse(entry).success; }; export { AudienceMapper, ExperienceMapper, isEntry };