UNPKG

input-spec

Version:

Zero-dependency TypeScript implementation of the Dynamic Input Field Specification Protocol with framework integration support

936 lines (930 loc) 31.5 kB
// src/types/index.ts function isInputFieldSpec(obj) { if (!obj || typeof obj !== "object") return false; if (!["STRING", "NUMBER", "DATE", "BOOLEAN"].includes(obj.dataType)) return false; if (!Array.isArray(obj.constraints)) return false; return typeof obj.displayName === "string" && typeof obj.expectMultipleValues === "boolean" && typeof obj.required === "boolean"; } function isValueAlias(obj) { if (!obj || typeof obj !== "object") { return false; } return obj.value !== void 0 && typeof obj.label === "string"; } function createDefaultValuesEndpoint(uri) { return { protocol: "HTTPS", uri, method: "GET", debounceMs: 300, minSearchLength: 0, // Provide a minimal default response mapping to satisfy tests & typical usage responseMapping: { dataField: "data" } }; } function buildInlineValuesEndpoint(items, mode = "CLOSED") { return { protocol: "INLINE", items, mode }; } function isAtomicConstraint(c) { return c.type !== void 0 && c.params !== void 0; } // src/validation/index.ts var FieldValidator = class { constructor(options = {}) { this.options = options; } async validate(fieldSpec, value, specificConstraintName) { const errors = []; value = this.applyCoercion(fieldSpec, value); if (fieldSpec.required && this.isEmpty(value)) { return { isValid: false, errors: [{ constraintName: "required", message: "This field is required", value }] }; } if (this.isEmpty(value)) { return { isValid: true, errors: [] }; } const typeErrors = this.typePhase(fieldSpec, value); if (typeErrors.length) { return { isValid: false, errors: typeErrors }; } const domain = await this.resolveMembership(fieldSpec.valuesEndpoint); if (domain && domain.closed) { this.checkMembership(fieldSpec, value, domain, errors); } for (const c of fieldSpec.constraints) { if (specificConstraintName && c.name !== specificConstraintName) continue; if (!isAtomicConstraint(c)) { this.applyLegacyConstraint(fieldSpec, value, c, errors); continue; } await this.applyAtomicConstraint(fieldSpec, value, c, errors); } return { isValid: errors.length === 0, errors }; } async validateAll(fieldSpec, value) { return this.validate(fieldSpec, value); } // --- Membership handling --- async resolveMembership(endpoint) { if (!endpoint) return void 0; const closed = endpoint.mode !== "SUGGESTIONS"; if (endpoint.protocol === "INLINE") { const set = new Set((endpoint.items || []).map((i) => i.value)); return { closed, values: set }; } return { closed, unresolved: true }; } checkMembership(fieldSpec, value, domain, errors) { if (domain.unresolved) { return; } const allowed = domain.values || /* @__PURE__ */ new Set(); if (fieldSpec.expectMultipleValues) { if (!Array.isArray(value)) { errors.push({ constraintName: "membership", message: "Expected array for multi-value field", value }); return; } value.forEach((v, idx) => { if (!allowed.has(v) && !this.looseMembershipMatch(v, allowed)) { errors.push({ constraintName: "membership", message: "Value not allowed", value: v, index: idx }); } }); } else { if (!allowed.has(value) && !this.looseMembershipMatch(value, allowed)) { errors.push({ constraintName: "membership", message: "Value not allowed", value }); } } } // Loose membership second-pass for coerced cases (string->number, string->boolean) looseMembershipMatch(value, allowed) { if (typeof value === "number") { for (const a of allowed) { if (typeof a === "string" && this.numericPattern().test(a) && Number(a) === value) return true; } } if (typeof value === "boolean") { for (const a of allowed) { if (typeof a === "string") { const lower = a.toLowerCase(); if (lower === "true" && value === true) return true; if (lower === "false" && value === false) return true; } } } return false; } applyCoercion(fieldSpec, value) { const fieldCfg = fieldSpec.coercion || {}; const coerceFlag = fieldCfg.coerce ?? this.options.coerce ?? false; const effective = { coerce: coerceFlag, trimStrings: this.options.trimStrings ?? fieldCfg.trimStrings ?? true, acceptNumericBoolean: this.options.acceptNumericBoolean ?? fieldCfg.acceptNumericBoolean ?? false, extraTrueValues: (this.options.extraTrueValues || []).concat(fieldCfg.extraTrueValues || []), extraFalseValues: (this.options.extraFalseValues || []).concat(fieldCfg.extraFalseValues || []), numberPattern: this.options.numberPattern || fieldCfg.numberPattern || this.numericPattern(), dateEpochSupport: this.options.dateEpochSupport ?? fieldCfg.dateEpochSupport ?? false }; if (!effective.coerce) return value; if (fieldSpec.expectMultipleValues) { if (!Array.isArray(value)) return value; return value.map((v) => this.coerceScalar(v, fieldSpec.dataType, effective)); } return this.coerceScalar(value, fieldSpec.dataType, effective); } numericPattern() { return /^[+-]?(\d+)(\.\d+)?$/; } coerceScalar(v, dataType, opt) { if (v == null) return v; if (typeof v === "string" && opt.trimStrings) v = v.trim(); switch (dataType) { case "NUMBER": if (typeof v === "string" && opt.numberPattern.test(v)) { const normalized = v.replace(/_/g, ""); const n = Number(normalized); return isNaN(n) ? v : n; } return v; case "BOOLEAN": if (typeof v === "string") { const lower = v.toLowerCase(); if (lower === "true") return true; if (lower === "false") return false; if (opt.acceptNumericBoolean) { if (lower === "1") return true; if (lower === "0") return false; } if (opt.extraTrueValues.includes(lower)) return true; if (opt.extraFalseValues.includes(lower)) return false; } return v; case "DATE": if (typeof v === "string" && opt.dateEpochSupport && /^\d+$/.test(v)) { const num = Number(v); const ms = v.length <= 10 ? num * 1e3 : num; const d = new Date(ms); if (!isNaN(d.getTime())) return d.toISOString(); } return v; case "STRING": default: return v; } } // --- Atomic constraint evaluation --- async applyAtomicConstraint(fieldSpec, rawValue, c, errors) { if (fieldSpec.expectMultipleValues) { if (!Array.isArray(rawValue)) { errors.push({ constraintName: c.name, message: "Expected array", value: rawValue }); return; } if ((c.type === "minValue" || c.type === "maxValue") && fieldSpec.dataType === "STRING") { const count = rawValue.length; if (c.type === "minValue" && typeof c.params?.value === "number" && count < c.params.value) { errors.push({ constraintName: c.name, message: c.errorMessage || `Minimum ${c.params.value} items`, value: rawValue }); } if (c.type === "maxValue" && typeof c.params?.value === "number" && count > c.params.value) { errors.push({ constraintName: c.name, message: c.errorMessage || `Maximum ${c.params.value} items`, value: rawValue }); } return; } rawValue.forEach((v, idx) => { const msgs2 = this.evaluateAtomic(v, c, fieldSpec.dataType); msgs2.forEach((m) => errors.push({ constraintName: c.name, message: m, value: v, index: idx })); }); return; } const msgs = this.evaluateAtomic(rawValue, c, fieldSpec.dataType); msgs.forEach((m) => errors.push({ constraintName: c.name, message: m, value: rawValue })); } evaluateAtomic(value, c, dataType) { switch (c.type) { case "pattern": if (typeof value !== "string") return []; try { const { regex, flags } = c.params || {}; if (!regex) return []; const r = new RegExp(regex, flags); if (!r.test(value)) return [c.errorMessage || "Invalid format"]; return []; } catch { return [c.errorMessage || "Invalid pattern"]; } case "minLength": if (typeof value !== "string") return []; if (value.length < c.params?.value) return [c.errorMessage || `Minimum ${c.params.value} characters`]; return []; case "maxLength": if (typeof value !== "string") return []; if (value.length > c.params?.value) return [c.errorMessage || `Maximum ${c.params.value} characters`]; return []; case "minValue": if (dataType !== "NUMBER" || typeof value !== "number") return []; if (value < c.params?.value) return [c.errorMessage || `Minimum value is ${c.params.value}`]; return []; case "maxValue": if (dataType !== "NUMBER" || typeof value !== "number") return []; if (value > c.params?.value) return [c.errorMessage || `Maximum value is ${c.params.value}`]; return []; case "minDate": if (dataType !== "DATE") return []; if (this.invalidDate(value)) return [c.errorMessage || "Invalid date"]; if (new Date(value) < new Date(c.params?.iso)) return [c.errorMessage || `Date must be after ${c.params.iso}`]; return []; case "maxDate": if (dataType !== "DATE") return []; if (this.invalidDate(value)) return [c.errorMessage || "Invalid date"]; if (new Date(value) > new Date(c.params?.iso)) return [c.errorMessage || `Date must be before ${c.params.iso}`]; return []; case "range": if (dataType === "NUMBER" && typeof value === "number") { const { min, max } = c.params || {}; if (min !== void 0 && value < min) return [c.errorMessage || `Must be \u2265 ${min}`]; if (max !== void 0 && value > max) return [c.errorMessage || `Must be \u2264 ${max}`]; return []; } if (dataType === "DATE") { if (this.invalidDate(value)) return [c.errorMessage || "Invalid date"]; const { min, max } = c.params || {}; const d = new Date(value); if (min && d < new Date(min)) return [c.errorMessage || `Date must be after ${min}`]; if (max && d > new Date(max)) return [c.errorMessage || `Date must be before ${max}`]; return []; } return []; case "custom": return []; default: return []; } } // Legacy descriptor support (best-effort) /** * @deprecated Legacy composite constraint + enumValues adapter. This exists solely to support * migration tests for v1 -> v2. It will be removed in the next major (3.0.0) once downstream * projects have adopted pure atomic constraints plus `valuesEndpoint` membership. * * Replacement strategy: * - Replace legacy `enumValues` with an INLINE `valuesEndpoint` definition. * - Split combined min/max or pattern properties into discrete atomic constraint descriptors * (e.g. { type: 'minLength' }, { type: 'maxLength' }, { type: 'pattern' }). * - Move any legacy `format` field to the field-level `formatHint` (non-failing advisory). * - Use the exported `migrateV1Spec` helper for automated transformation when feasible. */ applyLegacyConstraint(fieldSpec, value, legacy, errors) { const anyLegacy = legacy; if (anyLegacy.enumValues && Array.isArray(anyLegacy.enumValues)) { const set = new Set(anyLegacy.enumValues.map((v) => v.value)); const valuesToCheck = fieldSpec.expectMultipleValues && Array.isArray(value) ? value : [value]; valuesToCheck.forEach((v, idx) => { if (!set.has(v)) { if (fieldSpec.expectMultipleValues) { errors.push({ constraintName: legacy.name, message: anyLegacy.errorMessage || "Value not allowed", value: v, index: idx }); } else { errors.push({ constraintName: legacy.name, message: anyLegacy.errorMessage || "Value not allowed", value: v }); } } }); } if (legacy.pattern) { const pattern = legacy.pattern; try { const r = new RegExp(pattern); const valuesToCheck = fieldSpec.expectMultipleValues && Array.isArray(value) ? value : [value]; valuesToCheck.forEach((v, idx) => { if (typeof v !== "string" || !r.test(v)) { const constraintName = fieldSpec.expectMultipleValues ? `${legacy.name}[${idx}]` : legacy.name; if (fieldSpec.expectMultipleValues) { errors.push({ constraintName, message: legacy.errorMessage || "Invalid format", value: v, index: idx }); } else { errors.push({ constraintName, message: legacy.errorMessage || "Invalid format", value: v }); } } }); } catch { errors.push({ constraintName: legacy.name, message: legacy.errorMessage || "Invalid pattern", value }); } } if (legacy.min !== void 0 || legacy.max !== void 0) { const min = legacy.min; const max = legacy.max; if (fieldSpec.expectMultipleValues) { if (!Array.isArray(value)) { errors.push({ constraintName: legacy.name, message: "Expected an array", value }); } else { if (typeof min === "number" && value.length < min) { errors.push({ constraintName: legacy.name, message: legacy.errorMessage || `Minimum ${min} items`, value }); } if (typeof max === "number" && value.length > max) { errors.push({ constraintName: legacy.name, message: legacy.errorMessage || `Maximum ${max} items`, value }); } } } else if (fieldSpec.dataType === "STRING") { const combinedMessage = legacy.errorMessage; const violations = []; if (typeof min === "number" && typeof value === "string" && value.length < min) violations.push("min"); if (typeof max === "number" && typeof value === "string" && value.length > max) violations.push("max"); if (violations.length) { errors.push({ constraintName: legacy.name, message: combinedMessage || (violations[0] === "min" ? `Minimum ${min} characters` : `Maximum ${max} characters`), value }); } if (!combinedMessage) { if (typeof min === "number") this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_minLength", type: "minLength", params: { value: min } }, errors); if (typeof max === "number") this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_maxLength", type: "maxLength", params: { value: max } }, errors); } } else if (fieldSpec.dataType === "NUMBER") { if (typeof min === "number") this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_minValue", type: "minValue", params: { value: min } }, errors); if (typeof max === "number") this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_maxValue", type: "maxValue", params: { value: max } }, errors); } else if (fieldSpec.dataType === "DATE") { if (min !== void 0) this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_minDate", type: "minDate", params: { iso: min } }, errors); if (max !== void 0) this.applyAtomicConstraint(fieldSpec, value, { name: legacy.name + "_maxDate", type: "maxDate", params: { iso: max } }, errors); } } } typePhase(fieldSpec, value) { const errs = []; const { dataType, expectMultipleValues } = fieldSpec; if (expectMultipleValues) { if (!Array.isArray(value)) { errs.push({ constraintName: "type", message: "Expected an array", value }); return errs; } const base = fieldSpec.displayName.split(/\s+/)[0].toLowerCase(); value.forEach((v, idx) => { if (!this.scalarTypeOk(v, dataType)) { errs.push({ constraintName: `${base}[${idx}]`, message: `${dataType.toLowerCase()} type expected`, value: v, index: idx }); } }); return errs; } if (!this.scalarTypeOk(value, dataType)) { errs.push({ constraintName: "type", message: `${dataType.toLowerCase()} type expected`, value }); } return errs; } scalarTypeOk(value, dataType) { switch (dataType) { case "STRING": return typeof value === "string"; case "NUMBER": return typeof value === "number" && !isNaN(value); case "BOOLEAN": return typeof value === "boolean"; case "DATE": return !isNaN(new Date(value).getTime()); default: return false; } } isEmpty(value) { return value === null || value === void 0 || value === "" || Array.isArray(value) && value.length === 0; } invalidDate(value) { return isNaN(new Date(value).getTime()); } }; async function validateField(fieldSpec, value, constraintName) { const validator = new FieldValidator(); return validator.validate(fieldSpec, value, constraintName); } async function validateAllConstraints(fieldSpec, value) { const validator = new FieldValidator(); return validator.validateAll(fieldSpec, value); } // src/client/implementations.ts var FetchHttpClient = class { constructor(baseTimeout = 5e3) { this.baseTimeout = baseTimeout; } async request(url, options) { const controller = new AbortController(); const timeout = options.timeout || this.baseTimeout; const timeoutId = setTimeout(() => controller.abort(), timeout); try { const requestUrl = this.buildUrl(url, options.params); const fetchOptions = { method: options.method, signal: controller.signal }; if (options.headers) { fetchOptions.headers = options.headers; } if (options.body) { fetchOptions.body = JSON.stringify(options.body); fetchOptions.headers = { "Content-Type": "application/json", ...options.headers }; } const response = await fetch(requestUrl, fetchOptions); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } finally { clearTimeout(timeoutId); } } buildUrl(baseUrl, params) { if (!params || Object.keys(params).length === 0) { return baseUrl; } const url = new URL(baseUrl); Object.entries(params).forEach(([key, value]) => { if (value !== void 0 && value !== null) { url.searchParams.append(key, String(value)); } }); return url.toString(); } }; var NodeHttpClient = class { async request(url, options) { const fetchClient = new FetchHttpClient(); return fetchClient.request(url, options); } }; var MemoryCacheProvider = class { constructor() { this.cache = /* @__PURE__ */ new Map(); } get(key) { const item = this.cache.get(key); if (!item) { return null; } if (item.expiry && Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.value; } set(key, value, ttlMs) { const item = { value }; if (ttlMs !== void 0) { item.expiry = Date.now() + ttlMs; } this.cache.set(key, item); } delete(key) { this.cache.delete(key); } clear() { this.cache.clear(); } // Helper method for pattern-based clearing clearByPattern(pattern) { const keys = Array.from(this.cache.keys()); keys.filter((key) => key.includes(pattern)).forEach((key) => this.cache.delete(key)); } }; var NullCacheProvider = class { get() { return null; } set(_key, _value, _ttlMs) { } delete() { } clear() { } }; function createValuesResolver(config = {}) { let httpClient; switch (config.httpImplementation) { case "node": httpClient = new NodeHttpClient(); break; case "fetch": default: httpClient = new FetchHttpClient(config.httpTimeout); break; } let cacheProvider; switch (config.cacheStrategy) { case "null": cacheProvider = new NullCacheProvider(); break; case "memory": default: cacheProvider = new MemoryCacheProvider(); break; } return { httpClient, cacheProvider }; } // src/client/framework-adapters.ts var AngularHttpClientAdapter = class { constructor(angularHttpClient) { this.angularHttpClient = angularHttpClient; } async request(url, options) { const angularOptions = { headers: options.headers || {}, params: options.params || {} }; const observable = this.angularHttpClient.request( options.method, url, { ...angularOptions, body: options.body, responseType: "json" } ); return new Promise((resolve, reject) => { const subscription = observable.subscribe({ next: (data) => { subscription.unsubscribe(); resolve(data); }, error: (error) => { subscription.unsubscribe(); reject(error); } }); }); } }; var AxiosHttpClientAdapter = class { constructor(axiosInstance) { this.axiosInstance = axiosInstance; } async request(url, options) { const axiosConfig = { method: options.method.toLowerCase(), url, headers: options.headers, params: options.params, data: options.body, timeout: options.timeout }; const response = await this.axiosInstance.request(axiosConfig); return response.data; } }; var HttpClientFactory = class { /** * Create an HTTP client using Angular's HttpClient * Perfect for Angular applications with interceptors */ static createAngularAdapter(angularHttpClient) { return new AngularHttpClientAdapter(angularHttpClient); } /** * Create an HTTP client using an Axios instance * Perfect for applications already using Axios with custom configuration */ static createAxiosAdapter(axiosInstance) { return new AxiosHttpClientAdapter(axiosInstance); } /** * Create a native fetch-based HTTP client * Good for applications without specific HTTP client requirements */ static createFetchAdapter(baseConfig) { return new ConfigurableFetchHttpClient(baseConfig); } /** * Auto-detect and create appropriate HTTP client * Tries to detect existing HTTP client instances in the environment */ static createAuto() { if (typeof window !== "undefined" && window.ng) { console.warn("Angular detected but HttpClient not provided. Use createAngularAdapter() for better integration."); } return new ConfigurableFetchHttpClient(); } }; var ConfigurableFetchHttpClient = class { constructor(baseConfig = {}, options = {}) { this.baseConfig = baseConfig; this.defaultHeaders = options.defaultHeaders || {}; this.baseTimeout = options.timeout || 3e4; this.interceptors = options.interceptors || []; this.errorHandlers = options.errorHandlers || []; } async request(url, options) { let finalOptions = { ...options }; for (const interceptor of this.interceptors) { finalOptions = await interceptor(url, finalOptions); } const requestInit = { ...this.baseConfig, method: finalOptions.method, headers: { "Content-Type": "application/json", ...this.defaultHeaders, ...finalOptions.headers } }; if (finalOptions.body && ["POST", "PUT"].includes(finalOptions.method)) { requestInit.body = JSON.stringify(finalOptions.body); } const finalUrl = this.buildUrlWithParams(url, finalOptions.params); const controller = new AbortController(); const timeout = finalOptions.timeout || this.baseTimeout; const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(finalUrl, { ...requestInit, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { const error = new HttpError( `HTTP ${response.status}: ${response.statusText}`, response.status, response.statusText ); for (const handler of this.errorHandlers) { await handler(error, response); } throw error; } const data = await response.json(); return data; } catch (error) { clearTimeout(timeoutId); if (error instanceof HttpError) { throw error; } const httpError = new HttpError( error instanceof Error ? error.message : "Network error", 0, "Network Error" ); for (const handler of this.errorHandlers) { await handler(httpError); } throw httpError; } } /** * Add a request interceptor * Useful for adding authentication tokens, logging, etc. */ addInterceptor(interceptor) { this.interceptors.push(interceptor); } /** * Add an error handler * Useful for global error handling, logging, retries, etc. */ addErrorHandler(handler) { this.errorHandlers.push(handler); } buildUrlWithParams(url, params) { if (!params || Object.keys(params).length === 0) { return url; } const urlObj = new URL(url); Object.entries(params).forEach(([key, value]) => { if (value !== void 0 && value !== null) { urlObj.searchParams.append(key, String(value)); } }); return urlObj.toString(); } }; var HttpError = class extends Error { constructor(message, status, statusText) { super(message); this.status = status; this.statusText = statusText; this.name = "HttpError"; } }; var FrameworkCacheAdapter = class { constructor(cacheImplementation) { this.cacheImplementation = cacheImplementation; } get(key) { return this.cacheImplementation.get ? this.cacheImplementation.get(key) : null; } set(key, value, ttlMs) { if (this.cacheImplementation.set) { this.cacheImplementation.set(key, value, ttlMs); } } delete(key) { if (this.cacheImplementation.delete) { this.cacheImplementation.delete(key); } } clear() { if (this.cacheImplementation.clear) { this.cacheImplementation.clear(); } } }; // src/client/index.ts var ValuesResolver = class { constructor(httpClient, cache) { this.httpClient = httpClient; this.cache = cache; } /** * Résout les valeurs pour un endpoint donné * Gère: debouncing, cache, pagination, search */ async resolveValues(endpoint, options = {}) { const debounceMs = endpoint.debounceMs ?? 0; if (options.search !== void 0 && debounceMs > 0) { return this.debouncedResolve(endpoint, options); } return this.performResolve(endpoint, options); } debouncedResolve(endpoint, options) { return this.performResolve(endpoint, options); } async performResolve(endpoint, options) { if (endpoint.protocol === "INLINE") { const items = endpoint.items || []; return { values: items, hasNext: false, total: items.length, page: 1 }; } if (!endpoint.uri) { throw new Error("ValuesEndpoint uri is required for remote protocols"); } const cacheKey = this.buildCacheKey(endpoint, options); const cached = this.getFromCache(cacheKey, endpoint.cacheStrategy); if (cached) { return cached; } if (options.search !== void 0 && options.search.length < (endpoint.minSearchLength ?? 0)) { return { values: [], hasNext: false, total: 0 }; } try { const params = this.buildRequestParams(endpoint, options); const response = await this.httpClient.request(endpoint.uri, { method: endpoint.method, params }); const result = this.parseResponse(response, endpoint); this.setCache(cacheKey, result, endpoint.cacheStrategy); return result; } catch (error) { throw new Error(`Failed to resolve values: ${error}`); } } buildCacheKey(endpoint, options) { const parts = [ endpoint.uri, options.page || 1, options.search || "", options.limit || endpoint.requestParams?.defaultLimit || 50 ]; return parts.join("|"); } buildRequestParams(endpoint, options) { const params = {}; if (!endpoint.requestParams) { return params; } if (endpoint.paginationStrategy === "PAGE_NUMBER" && options.page !== void 0) { if (endpoint.requestParams.pageParam) { params[endpoint.requestParams.pageParam] = options.page; } } if (endpoint.requestParams.limitParam) { params[endpoint.requestParams.limitParam] = options.limit || endpoint.requestParams.defaultLimit || 50; } if (endpoint.requestParams.searchParam && options.search) { params[endpoint.requestParams.searchParam] = options.search; } return params; } parseResponse(data, endpoint) { const mapping = endpoint.responseMapping || { dataField: "data" }; let values = []; if (mapping.dataField && data && typeof data === "object" && data[mapping.dataField] !== void 0) { values = data[mapping.dataField] || []; } else if (Array.isArray(data)) { values = data; } const hasNext = mapping.hasNextField && data ? Boolean(data[mapping.hasNextField]) : false; const total = mapping.totalField && data ? data[mapping.totalField] : void 0; const page = mapping.pageField && data ? data[mapping.pageField] : void 0; return { values, hasNext, total, page }; } getFromCache(key, strategy) { if (!strategy || strategy === "NONE") { return null; } return this.cache.get(key); } setCache(key, value, strategy) { if (!strategy || strategy === "NONE") { return; } let ttlMs; switch (strategy) { case "SESSION": ttlMs = void 0; break; case "SHORT_TERM": ttlMs = 5 * 60 * 1e3; break; case "LONG_TERM": ttlMs = 60 * 60 * 1e3; break; default: return; } this.cache.set(key, value, ttlMs); } /** * Clear cache for a specific endpoint */ clearCacheForEndpoint(_endpoint) { this.cache.clear(); } }; // src/index.ts var PROTOCOL_VERSION = "2.0.0"; var LIBRARY_VERSION = "2.0.0"; export { AngularHttpClientAdapter, AxiosHttpClientAdapter, ConfigurableFetchHttpClient, FetchHttpClient, FieldValidator, FrameworkCacheAdapter, HttpClientFactory, HttpError, LIBRARY_VERSION, MemoryCacheProvider, NodeHttpClient, NullCacheProvider, PROTOCOL_VERSION, ValuesResolver, buildInlineValuesEndpoint, createDefaultValuesEndpoint, createValuesResolver, isAtomicConstraint, isInputFieldSpec, isValueAlias, validateAllConstraints, validateField };