feat(platform): add managed data product history plane

This commit is contained in:
Codex
2026-07-18 14:38:06 +03:00
parent 02816c4352
commit 3c5d8f6cef
34 changed files with 2091 additions and 163 deletions
@@ -118,6 +118,19 @@ export function normalizeSnapshotFacts(value: unknown): IDataObject[] {
});
}
export function normalizeHistoryResponse(value: unknown): IDataObject {
if (!isObject(value) || value.schemaVersion !== 'nodedc.data-product.history/v1'
|| !isObject(value.dataProduct) || !isObject(value.query) || !Array.isArray(value.facts)) {
throw new Error('data_product_history_invalid');
}
value.facts.forEach((fact, index) => {
if (!isObject(fact) || Number.isNaN(Date.parse(String(fact.bucketStart ?? '')))) {
throw new Error(`history_fact_${index}_invalid`);
}
});
return value;
}
export function snapshotReadParameters(
dataProductId: unknown,
pageSize: unknown,
@@ -132,6 +145,52 @@ export function snapshotReadParameters(
};
}
export function historyReadParameters(value: {
dataProductId: unknown;
from: unknown;
to: unknown;
resolutionMs: unknown;
sourceIds: unknown;
limit: unknown;
cursor: unknown;
}): {
encodedProductId: string;
from: string;
to: string;
resolutionMs: number;
sourceIds: string[];
limit: number;
cursor: string;
} {
const from = requireIsoTimestamp(value.from, 'from');
const to = requireIsoTimestamp(value.to, 'to');
if (Date.parse(from) >= Date.parse(to)) throw new Error('history_range_invalid');
const resolutionMs = Number(value.resolutionMs);
if (!Number.isInteger(resolutionMs) || resolutionMs < 1000 || resolutionMs > 24 * 60 * 60 * 1000) {
throw new Error('history_resolution_invalid');
}
const limit = Number(value.limit);
if (!Number.isInteger(limit) || limit < 1 || limit > 5000) throw new Error('history_limit_invalid');
const sourceIds = [...new Set((Array.isArray(value.sourceIds)
? value.sourceIds
: String(value.sourceIds ?? '').split(','))
.map((item) => String(item ?? '').trim())
.filter(Boolean)
.map((item) => requireIdentifier(item, 'sourceIds')))];
if (sourceIds.length > 1000) throw new Error('history_source_ids_limit_exceeded');
const cursor = String(value.cursor ?? '').trim();
if (cursor && (cursor.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(cursor))) throw new Error('history_cursor_invalid');
return {
encodedProductId: encodeIdentifierPath(value.dataProductId, 'dataProductId'),
from,
to,
resolutionMs,
sourceIds,
limit,
cursor,
};
}
export function dataProductOptions(value: unknown): INodeListSearchItems[] {
if (!isObject(value)) throw new Error('data_product_catalog_invalid');
const rows = Array.isArray(value.dataProducts)