@ninetailed/experience.js-utils-contentful
Version:
Contentful utilities for Experience.js
276 lines (266 loc) • 7.75 kB
JavaScript
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 };