@localazy/cdn-client
Version:
Node.js module that allows you to easily interact with the Localazy CDN.
414 lines (413 loc) • 12.5 kB
JavaScript
/* @localazy/cdn-client@1.5.16
* (c) 2026 Localazy <team@localazy.com>
* @license MIT */
var e = class {
context;
constructor(e) {
this.context = e;
}
async fetchLocale(e) {
return this.context.cache.has(e) ? new Promise((t) => {
t(this.context.cache.get(e));
}) : this.context.client.get(e.metafileLocale.uri);
}
async fetchMetafile() {
return await this.context.client.get(this.context.metafile.params.jsonPath);
}
}, t = class {
map;
constructor() {
this.map = /* @__PURE__ */ new Map();
}
get(e) {
return this.map.get(e);
}
has(e) {
return this.map.has(e);
}
set(e, t) {
this.map.set(e, t);
}
flush() {
this.map = /* @__PURE__ */ new Map();
}
}, s = (e) => "string" == typeof e, a = (e) => void 0 === e, i = (e) => Array.isArray(e), l = (e) => "[object Object]" === Object.prototype.toString.call(e), r = (e) => [...new Set(e)], o = (e, t) => {
const s = {};
return e.filter((e) => {
const a = t(e);
return !Object.hasOwn(s, a) && (s[a] = !0, !0);
});
}, c = class {
context;
cacheAdapter;
constructor(e) {
this.context = e, this.cacheAdapter = new t();
}
setIfMissed(e) {
const { metafileFile: t, metafileLocale: s, data: a } = e, i = this.keyFromMetafile({
metafileFile: t,
metafileLocale: s
});
this.cacheAdapter.has(i) || this.cacheAdapter.set(i, a);
}
has(e) {
const t = this.keyFromMetafile(e);
return this.cacheAdapter.has(t);
}
get(e) {
const t = this.keyFromMetafile(e);
return this.cacheAdapter.get(t);
}
flush() {
this.cacheAdapter.flush();
}
keyFromMetafile(e) {
const { metafileFile: t, metafileLocale: s } = e, a = [...r(t.productFlavors)].sort().join("-");
return [
this.context.metafile.params.cdnId,
t.id,
t.file,
t.path,
t.library,
t.module,
t.buildType,
a,
s.locale,
s.timestamp.toString()
].filter((e) => "" !== e).join("-");
}
}, n = class e {
context;
constructor(e) {
this.context = e;
}
createCdnResponse(t) {
const { requests: s, responses: a, hasSingleFileResponse: i, hasSingleLocaleResponse: l } = t;
return 0 === a.length || void 0 === a[0] ? {} : (this.cacheResponses(s, a), i && l ? a[0] : e.transformResponses(t));
}
cacheResponses(e, t) {
t.forEach((t, s) => {
if (void 0 !== e[s]) {
const { metafileFile: a, metafileLocale: i } = e[s];
a && i && this.context.cache.setIfMissed({
metafileFile: a,
metafileLocale: i,
data: t
});
}
});
}
static transformResponses(e) {
const { requests: t, responses: s, hasSingleFileResponse: a } = e;
return s.reduce((e, s, i) => {
if (void 0 !== t[i]) {
const { metafileFile: l, metafileLocale: r } = t[i];
l && r && (a ? e[r.locale] = s : (e[l.id] || (e[l.id] = {}), e[l.id][r.locale] = s));
}
return e;
}, {});
}
}, h = class {
metafile;
cdn;
client;
api;
cache;
responseFactory;
constructor(t) {
this.metafile = t.metafileContext, this.cdn = t.cdn, this.client = t.client, this.api = new e(this), this.cache = new c(this), this.responseFactory = new n(this);
}
}, u = class {
id;
file;
path;
library;
module;
buildType;
timestamp;
productFlavors;
locales;
baseUrl;
constructor(e) {
this.id = e.id, this.file = e.file, this.path = e.path, this.library = e.library, this.module = e.module, this.buildType = e.buildType, this.timestamp = e.timestamp, this.productFlavors = e.productFlavors, this.locales = e.locales, this.baseUrl = e.baseUrl;
}
toCdnFile() {
return {
id: this.id,
file: this.file,
path: this.path,
library: this.library,
module: this.module,
buildType: this.buildType,
productFlavors: this.productFlavors,
locales: this.locales.map((e) => ({
locale: e.locale,
isBaseLocale: e.isBaseLocale,
uri: `${this.baseUrl}${e.uri}`
}))
};
}
}, p = class {
language;
region;
script;
isRtl;
name;
localizedName;
uri;
timestamp;
baseLocale;
constructor(e, t) {
this.language = e.language, this.region = e.region, this.script = e.script, this.isRtl = e.isRtl, this.name = e.name, this.localizedName = e.localizedName, this.uri = e.uri, this.timestamp = e.timestamp, this.baseLocale = t;
}
get locale() {
return this.language && this.region && this.script ? `${this.language}_${this.region}#${this.script}` : this.language && this.script ? `${this.language}#${this.script}` : this.language && this.region ? `${this.language}_${this.region}` : this.language;
}
get isBaseLocale() {
return this.locale === this.baseLocale;
}
toCdnLocale() {
return {
locale: this.locale,
isBaseLocale: this.isBaseLocale,
language: this.language,
region: this.region,
script: this.script,
isRtl: this.isRtl,
name: this.name,
localizedName: this.localizedName
};
}
}, d = class e {
projectUrl;
baseLocale;
locales;
timestamp;
files;
filesMap;
constructor(t, s) {
this.projectUrl = t.projectUrl, this.timestamp = t.timestamp, this.files = e.filesFactory(t.files, t.baseLocale, s), this.filesMap = e.filesMapFactory(this.files), this.locales = e.localesFactory(this.files), this.baseLocale = this.locales.find((e) => e.isBaseLocale);
}
static createEmpty(t) {
return new e({
projectUrl: "",
baseLocale: "",
timestamp: 0,
files: {}
}, t);
}
static filesFactory(e, t, s) {
return Object.keys(e).reduce((a, i) => {
if (void 0 !== e[i]) {
const l = e[i].locales.map((e) => new p(e, t));
a.push(new u({
...e[i],
id: i,
locales: l,
baseUrl: s.baseUrl
}));
}
return a;
}, []);
}
static filesMapFactory(e) {
return e.reduce((e, t) => (e[t.id] = t, e), {});
}
static localesFactory(e) {
return o(e.reduce((e, t) => (e.push(...t.locales.map((e) => e.toCdnLocale())), e), []), (e) => e.locale);
}
}, f = class e {
options;
constructor(t) {
this.options = e.parseMetafileUrl(t);
}
get url() {
return this.options.url;
}
get baseUrl() {
return this.options.baseUrl;
}
get cdnId() {
return this.options.cdnId;
}
get jsonPath() {
return this.options.jsonPath;
}
static parseMetafileUrl(e) {
let t;
try {
t = new URL(e);
} catch {
throw new Error("Invalid param: \"options.metafile\" cannot be parsed as url.");
}
const s = /^\/(.*?)\/(.*?)\.(v2.json|js|ts)$/.exec(t.pathname);
if (null === s || 4 !== s.length || void 0 === s[1] || void 0 === s[2]) throw new Error("Invalid param: \"options.metafile\" contains invalid metafile url.");
const a = s[1], i = s[2];
return {
url: e,
baseUrl: t.origin,
cdnId: a,
jsonPath: `/${a}/${i}.v2.json`
};
}
}, m = class {
params;
parsedData;
data;
constructor(e) {
this.params = new f(e.metafile), this.parsedData = null, this.data = d.createEmpty(this.params);
}
setMetafile(e) {
this.parsedData = e, this.data = new d(e, this.params);
}
}, g = class {
baseUrl;
constructor(e) {
this.baseUrl = e;
}
async get(e) {
const t = await fetch(`${this.baseUrl}${e}`), s = t.headers.get("content-type"), a = "application/json5" === s || "application/json" === s;
if (t.status >= 400) throw new Error(`Request failed with status code ${t.status.toString()}`);
let i;
return i = a ? await t.json() : await t.text(), i;
}
}, w = class {
context;
constructor(e) {
this.context = e;
}
}, x = class extends w {
flush = () => {
this.context.cache.flush();
};
}, y = class extends w {
get projectUrl() {
return this.context.metafile.data.projectUrl;
}
get baseLocale() {
return this.context.metafile.data.baseLocale;
}
get url() {
return this.context.metafile.params.url;
}
get files() {
return this.context.metafile.data.files.map((e) => e.toCdnFile());
}
locales = (e) => {
const { excludeBaseLocale: t } = e || {}, { locales: s } = this.context.metafile.data;
return t ? s.filter((e) => !e.isBaseLocale) : s;
};
refresh = async () => {
const e = await this.context.api.fetchMetafile();
this.context.metafile.setMetafile(e);
};
switch = async (e) => {
this.context.metafile.params = new f(e.metafile), await this.refresh();
};
}, b = class {
data;
context;
constructor(e) {
this.context = e.context, this.data = e.data || {};
}
}, F = class {
files;
localesMap;
hasSingleFileResponse;
hasSingleLocaleResponse;
context;
constructor(e) {
this.files = [], this.localesMap = new b({ context: e }), this.hasSingleFileResponse = !1, this.hasSingleLocaleResponse = !1, this.context = e;
}
async execute() {
const e = this.getPromises(), t = e.map((e) => e[0]), s = e.map((e) => e[1]), a = await Promise.all(t);
return this.context.responseFactory.createCdnResponse({
requests: s,
responses: a,
localesMap: this.localesMap,
hasSingleFileResponse: this.hasSingleFileResponse,
hasSingleLocaleResponse: this.hasSingleLocaleResponse
});
}
getPromises() {
return this.files.reduce((e, t) => (void 0 !== this.localesMap.data?.[t.id] && e.push(...this.localesMap.data[t.id].map((e) => {
const s = {
metafileFile: t,
metafileLocale: e
};
return [this.context.api.fetchLocale(s), s];
})), e), []);
}
}, L = class {
context;
request;
constructor(e) {
this.context = e, this.request = new F(this.context);
}
addFiles(e) {
if (!(l(e) || s(e) || a(e) || i(e))) throw new Error("Invalid param: \"request.files\" must be object, array, string or undefined.");
if (i(e) && e.forEach((e) => {
if (!l(e) && !s(e)) throw new Error("Invalid param: array \"request.files\" must contain objects or strings.");
}), s(e)) {
this.request.hasSingleFileResponse = !0;
const t = this.context.metafile.data.files.find((t) => t.id === e);
if (!(t instanceof u)) throw new Error(`File not found: "${e}".`);
this.request.files = [t];
} else if (a(e)) this.request.files = [...this.context.metafile.data.files];
else if (i(e)) this.request.files = e.map((e) => {
let t;
if (s(e)) {
const s = this.context.metafile.data.files.find((t) => t.id === e);
if (a(s)) throw new Error(`File not found: "${e}".`);
t = s;
} else {
const s = this.context.metafile.data.files.find((t) => t.id === e.id);
if (a(s)) throw new Error(`File not found: "${e.id}".`);
t = s;
}
return t;
});
else if (l(e)) {
this.request.hasSingleFileResponse = !0;
const t = this.context.metafile.data.files.find((t) => t.id === e.id);
if (a(t)) throw new Error(`File not found: "${e.id}".`);
this.request.files = [t];
}
return this;
}
addLocales(e, t) {
if (!(s(e) || a(e) || i(e))) throw new Error("Invalid param: \"request.locales\" must be array, string or undefined.");
return i(e) && e.forEach((e) => {
if (!s(e)) throw new Error("Invalid param: array \"request.locales\" must contain strings.");
}), s(e) ? (this.request.hasSingleLocaleResponse = !0, this.request.files.reduce((t, s) => (t.data[s.id] = s.locales.filter((t) => t.locale === e), t), this.request.localesMap)) : a(e) ? this.request.files.reduce((e, s) => (e.data[s.id] = t ? s.locales.filter((e) => !e.isBaseLocale) : s.locales, e), this.request.localesMap) : i(e) && this.request.files.reduce((t, s) => (t.data[s.id] = s.locales.filter((t) => e.includes(t.locale)), t), this.request.localesMap), this;
}
getCdnRequest() {
const e = this.request;
return this.request = new F(this.context), e;
}
}, R = class e {
metafile;
cache;
context;
static version = "1.5.16";
constructor(e) {
const t = new m(e), s = new g(t.params.baseUrl);
this.context = new h({
metafileContext: t,
cdn: this,
client: s
}), this.metafile = new y(this.context), this.cache = new x(this.context);
}
fetch = async (e) => {
const { files: t, locales: s, excludeBaseLocale: a } = e || {};
return new L(this.context).addFiles(t).addLocales(s, a).getCdnRequest().execute();
};
static async create(t) {
if (!t) throw new Error("Invalid param: missing required \"options\" parameter.");
if (!s(t.metafile)) throw new Error("Invalid param: \"options.metafile\" must be string.");
const a = new e(t);
return await a.metafile.refresh(), a;
}
};
export { e as Api, w as CdnBase, x as CdnCache, R as CdnClient, y as CdnMetafile, h as Context, g as FetchHttpAdapter, c as LocalesCache, b as LocalesMap, t as MemoryCacheAdapter, m as MetafileContext, d as MetafileData, u as MetafileFile, p as MetafileLocale, f as MetafileParams, F as Request, L as RequestBuilder, n as ResponseFactory, i as isArray, l as isPlainObject, s as isString, a as isUndefined, r as uniq, o as uniqBy };
//# sourceMappingURL=localazy-cdn-client.min.js.map