@ats-cx/cx-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +23 -0
  2. package/dist/apm/client.d.ts +59 -0
  3. package/dist/apm/client.js +247 -0
  4. package/dist/apm/config-state.d.ts +19 -0
  5. package/dist/apm/config-state.js +18 -0
  6. package/dist/apm/config.d.ts +93 -0
  7. package/dist/apm/config.js +72 -0
  8. package/dist/apm/errors.d.ts +39 -0
  9. package/dist/apm/errors.js +39 -0
  10. package/dist/apm/portal.d.ts +20 -0
  11. package/dist/apm/portal.js +89 -0
  12. package/dist/apm/values.d.ts +13 -0
  13. package/dist/apm/values.js +58 -0
  14. package/dist/config.d.ts +22 -0
  15. package/dist/config.js +168 -0
  16. package/dist/db/client.d.ts +23 -0
  17. package/dist/db/client.js +21 -0
  18. package/dist/diff/project-diff.d.ts +125 -0
  19. package/dist/diff/project-diff.js +531 -0
  20. package/dist/index.d.ts +38 -0
  21. package/dist/index.js +23 -0
  22. package/dist/normalize/log.d.ts +12 -0
  23. package/dist/normalize/log.js +143 -0
  24. package/dist/normalize/project.d.ts +18 -0
  25. package/dist/normalize/project.js +254 -0
  26. package/dist/providers/log-provider.d.ts +27 -0
  27. package/dist/providers/log-provider.js +63 -0
  28. package/dist/providers/project-provider.d.ts +10 -0
  29. package/dist/providers/project-provider.js +22 -0
  30. package/dist/providers/remote-project-provider.d.ts +28 -0
  31. package/dist/providers/remote-project-provider.js +167 -0
  32. package/dist/providers/source-provider.d.ts +11 -0
  33. package/dist/providers/source-provider.js +33 -0
  34. package/dist/rules/engine.d.ts +13 -0
  35. package/dist/rules/engine.js +213 -0
  36. package/dist/run/result-schema.d.ts +98 -0
  37. package/dist/run/result-schema.js +50 -0
  38. package/dist/run/store.d.ts +23 -0
  39. package/dist/run/store.js +49 -0
  40. package/dist/semantics/resolver.d.ts +37 -0
  41. package/dist/semantics/resolver.js +220 -0
  42. package/dist/semantics/table-loader.d.ts +11 -0
  43. package/dist/semantics/table-loader.js +44 -0
  44. package/dist/semantics/table-schema.d.ts +30 -0
  45. package/dist/semantics/table-schema.js +186 -0
  46. package/dist/types.d.ts +164 -0
  47. package/dist/types.js +2 -0
  48. package/package.json +33 -0
@@ -0,0 +1,531 @@
1
+ import { normalizeProjectSnapshot } from "../normalize/project.js";
2
+ const PAGE_SECTION_LABELS = Object.freeze({
3
+ pages: "内页",
4
+ cover: "封面",
5
+ frontFlysheet: "前扉页",
6
+ backFlysheet: "后扉页",
7
+ edge: "书侧边",
8
+ });
9
+ /**
10
+ * 对比两个 project 快照。
11
+ *
12
+ * 输出重点不是“全文 diff”,而是诊断最关心的三类变化:
13
+ * - 页面增删
14
+ * - 元素增删
15
+ * - 元素几何 / crop / 图片引用变化
16
+ *
17
+ * 这样一来,日志里看到 `clickCropImage`、`transformElement`、`saveProject`
18
+ * 之后,就能直接检查这些动作是否真的改变了 project 状态。
19
+ */
20
+ export function diffProjectSnapshots(beforeRaw, afterRaw, options = {}) {
21
+ const before = normalizeProjectSnapshot(beforeRaw);
22
+ const after = normalizeProjectSnapshot(afterRaw);
23
+ const beforeProject = getProjectRoot(beforeRaw);
24
+ const afterProject = getProjectRoot(afterRaw);
25
+ const maxChanges = options.maxChanges || 100;
26
+ const beforePages = before.pages.map(enrichPageForDiff);
27
+ const afterPages = after.pages.map(enrichPageForDiff);
28
+ const beforePageMap = Object.fromEntries(beforePages.map((page) => [page.id || page.path, page]));
29
+ const afterPageMap = Object.fromEntries(afterPages.map((page) => [page.id || page.path, page]));
30
+ const beforeElementMap = Object.fromEntries(before.elements.map((element) => [element.lookupKey, element]));
31
+ const afterElementMap = Object.fromEntries(after.elements.map((element) => [element.lookupKey, element]));
32
+ const addedPages = diffAdded(afterPageMap, beforePageMap);
33
+ const removedPages = diffAdded(beforePageMap, afterPageMap);
34
+ const addedElements = diffAdded(afterElementMap, beforeElementMap);
35
+ const removedElements = diffAdded(beforeElementMap, afterElementMap);
36
+ const changedElements = diffChangedElements(beforeElementMap, afterElementMap).slice(0, maxChanges);
37
+ const changedProjectMeta = diffProjectMeta(before.projectMeta, after.projectMeta);
38
+ const pageSectionDiffs = diffPageSections(beforeProject, afterProject, maxChanges);
39
+ return {
40
+ generatedAt: new Date().toISOString(),
41
+ beforeSummary: summarizeSnapshot(before),
42
+ afterSummary: summarizeSnapshot(after),
43
+ pageSectionDiffs,
44
+ changedProjectMeta,
45
+ addedPages,
46
+ removedPages,
47
+ addedElements,
48
+ removedElements,
49
+ changedElements,
50
+ summary: {
51
+ addedPageCount: addedPages.length,
52
+ removedPageCount: removedPages.length,
53
+ addedElementCount: addedElements.length,
54
+ removedElementCount: removedElements.length,
55
+ changedElementCount: changedElements.length,
56
+ changedProjectMetaCount: changedProjectMeta.length,
57
+ },
58
+ };
59
+ }
60
+ /**
61
+ * projectMeta 顶层字段保持浅比较,但对 spec 额外展开一层,
62
+ * 让报告里的字段名能落成 `spec.size` 这种形式。
63
+ */
64
+ function diffProjectMeta(beforeMeta, afterMeta) {
65
+ const { spec: beforeSpec, ...beforeBaseMeta } = beforeMeta || {};
66
+ const { spec: afterSpec, ...afterBaseMeta } = afterMeta || {};
67
+ return [
68
+ ...diffFlatObject(beforeBaseMeta, afterBaseMeta),
69
+ ...diffFlatObject(beforeSpec || {}, afterSpec || {}, "spec"),
70
+ ];
71
+ }
72
+ /**
73
+ * 输出按页面区域划分的 diff。
74
+ *
75
+ * 这里补的是 project 原始结构里的几个关键区域:
76
+ * - pages: 内页
77
+ * - cover: 封面
78
+ * - frontFlysheet / backFlysheet: 前后扉页
79
+ * - edge: 书侧边
80
+ *
81
+ * 这样在排查“明明改了封面或扉页,但 diff 看不出来”的 case 时,
82
+ * 可以直接从结构化结果里看到具体区域发生了什么变化。
83
+ */
84
+ function diffPageSections(beforeProject, afterProject, maxChanges) {
85
+ return {
86
+ pages: diffCollectionSection({
87
+ key: "pages",
88
+ label: PAGE_SECTION_LABELS.pages,
89
+ beforeItems: toArray(beforeProject.pages),
90
+ afterItems: toArray(afterProject.pages),
91
+ beforeExists: hasOwn(beforeProject, "pages"),
92
+ afterExists: hasOwn(afterProject, "pages"),
93
+ pathPrefix: "project.pages",
94
+ maxChanges,
95
+ }),
96
+ cover: diffCoverSection(beforeProject.cover, afterProject.cover, maxChanges),
97
+ frontFlysheet: diffCollectionSection({
98
+ key: "frontFlysheet",
99
+ label: PAGE_SECTION_LABELS.frontFlysheet,
100
+ beforeItems: toArray(beforeProject.frontFlysheet),
101
+ afterItems: toArray(afterProject.frontFlysheet),
102
+ beforeExists: hasOwn(beforeProject, "frontFlysheet"),
103
+ afterExists: hasOwn(afterProject, "frontFlysheet"),
104
+ pathPrefix: "project.frontFlysheet",
105
+ maxChanges,
106
+ }),
107
+ backFlysheet: diffCollectionSection({
108
+ key: "backFlysheet",
109
+ label: PAGE_SECTION_LABELS.backFlysheet,
110
+ beforeItems: toArray(beforeProject.backFlysheet),
111
+ afterItems: toArray(afterProject.backFlysheet),
112
+ beforeExists: hasOwn(beforeProject, "backFlysheet"),
113
+ afterExists: hasOwn(afterProject, "backFlysheet"),
114
+ pathPrefix: "project.backFlysheet",
115
+ maxChanges,
116
+ }),
117
+ edge: diffCollectionSection({
118
+ key: "edge",
119
+ label: PAGE_SECTION_LABELS.edge,
120
+ beforeItems: toArray(beforeProject.edge),
121
+ afterItems: toArray(afterProject.edge),
122
+ beforeExists: hasOwn(beforeProject, "edge"),
123
+ afterExists: hasOwn(afterProject, "edge"),
124
+ pathPrefix: "project.edge",
125
+ maxChanges,
126
+ }),
127
+ };
128
+ }
129
+ /**
130
+ * 对数组型页面区域做 diff,例如 pages / frontFlysheet / backFlysheet / edge。
131
+ */
132
+ function diffCollectionSection({ key, label, beforeItems, afterItems, beforeExists, afterExists, pathPrefix, maxChanges, }) {
133
+ const beforeMap = buildSectionItemMap(beforeItems, key, label, pathPrefix);
134
+ const afterMap = buildSectionItemMap(afterItems, key, label, pathPrefix);
135
+ const addedItems = diffAddedSectionItems(afterMap, beforeMap);
136
+ const removedItems = diffAddedSectionItems(beforeMap, afterMap);
137
+ const changedItems = diffChangedSectionItems(beforeMap, afterMap, maxChanges);
138
+ return {
139
+ key,
140
+ label,
141
+ beforeExists,
142
+ afterExists,
143
+ beforeCount: beforeItems.length,
144
+ afterCount: afterItems.length,
145
+ addedItems,
146
+ removedItems,
147
+ changedItems,
148
+ summary: {
149
+ addedItemCount: addedItems.length,
150
+ removedItemCount: removedItems.length,
151
+ changedItemCount: changedItems.length,
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * cover 不是数组节点,而是一个对象,真正可承载元素的是 `cover.containers`。
157
+ * 所以这里拆成两层:
158
+ * - 顶层 cover 元数据变化
159
+ * - containers 内部各区域变化
160
+ */
161
+ function diffCoverSection(beforeCover, afterCover, maxChanges) {
162
+ const beforeMeta = normalizeCoverMeta(beforeCover);
163
+ const afterMeta = normalizeCoverMeta(afterCover);
164
+ const changedFields = diffFlatObject(beforeMeta.compareFields, afterMeta.compareFields);
165
+ const beforeCoverRecord = beforeCover || {};
166
+ const afterCoverRecord = afterCover || {};
167
+ const containerDiff = diffCollectionSection({
168
+ key: "cover",
169
+ label: PAGE_SECTION_LABELS.cover,
170
+ beforeItems: toArray(beforeCoverRecord.containers),
171
+ afterItems: toArray(afterCoverRecord.containers),
172
+ beforeExists: beforeMeta.exists && Array.isArray(beforeCoverRecord.containers),
173
+ afterExists: afterMeta.exists && Array.isArray(afterCoverRecord.containers),
174
+ pathPrefix: "project.cover.containers",
175
+ maxChanges,
176
+ });
177
+ return {
178
+ key: "cover",
179
+ label: PAGE_SECTION_LABELS.cover,
180
+ beforeExists: beforeMeta.exists,
181
+ afterExists: afterMeta.exists,
182
+ beforeCount: containerDiff.beforeCount,
183
+ afterCount: containerDiff.afterCount,
184
+ changedFields,
185
+ addedItems: containerDiff.addedItems,
186
+ removedItems: containerDiff.removedItems,
187
+ changedItems: containerDiff.changedItems,
188
+ summary: {
189
+ changedFieldCount: changedFields.length,
190
+ addedItemCount: containerDiff.summary.addedItemCount,
191
+ removedItemCount: containerDiff.summary.removedItemCount,
192
+ changedItemCount: containerDiff.summary.changedItemCount,
193
+ },
194
+ };
195
+ }
196
+ /**
197
+ * 规范化 cover 顶层字段,只比较对诊断有意义的元信息,
198
+ * 避免把整棵 cover 对象直接展开成噪音很多的深层 diff。
199
+ */
200
+ function normalizeCoverMeta(cover) {
201
+ const exists = Boolean(cover) && typeof cover === "object" && !Array.isArray(cover);
202
+ const safeCover = (exists ? cover : {});
203
+ return {
204
+ exists,
205
+ compareFields: {
206
+ width: getFieldValue(safeCover, "width"),
207
+ height: getFieldValue(safeCover, "height"),
208
+ bgImageUrl: getFieldValue(safeCover, "bgImageUrl"),
209
+ bleed: cloneObject(safeCover.bleed),
210
+ containerCount: Array.isArray(safeCover.containers) ? safeCover.containers.length : 0,
211
+ },
212
+ };
213
+ }
214
+ /**
215
+ * 建 section item 索引时,同时保留:
216
+ * - record: 输出给 diff JSON 的精简结构
217
+ * - compareFields: 实际参与字段比较的对象
218
+ */
219
+ function buildSectionItemMap(items, sectionKey, sectionLabel, pathPrefix) {
220
+ const map = {};
221
+ for (let index = 0; index < items.length; index += 1) {
222
+ const entry = normalizeSectionItem(items[index], index, sectionKey, sectionLabel, pathPrefix);
223
+ map[entry.record.lookupKey] = entry;
224
+ }
225
+ return map;
226
+ }
227
+ /**
228
+ * 规范化 pages / cover.containers / frontFlysheet / backFlysheet / edge 下的单个 item。
229
+ */
230
+ function normalizeSectionItem(rawItem, index, sectionKey, sectionLabel, pathPrefix) {
231
+ const safeItem = rawItem && typeof rawItem === "object" && !Array.isArray(rawItem) ? rawItem : {};
232
+ const path = `${pathPrefix}[${index}]`;
233
+ const compareFields = {
234
+ type: getFieldValue(safeItem, "type"),
235
+ width: getFieldValue(safeItem, "width"),
236
+ height: getFieldValue(safeItem, "height"),
237
+ bgColor: getFieldValue(safeItem, "bgColor"),
238
+ bgImageUrl: getFieldValue(safeItem, "bgImageUrl"),
239
+ sheetLocation: getFieldValue(safeItem, "sheetLocation"),
240
+ elementCount: Array.isArray(safeItem.elements) ? safeItem.elements.length : 0,
241
+ bleed: cloneObject(safeItem.bleed),
242
+ backend: cloneObject(safeItem.backend),
243
+ template: cloneObject(safeItem.template),
244
+ flysheetParams: cloneObject(safeItem.flysheetParams),
245
+ };
246
+ return {
247
+ record: {
248
+ lookupKey: getFieldValue(safeItem, "id") || path,
249
+ id: getFieldValue(safeItem, "id"),
250
+ path,
251
+ sectionKey,
252
+ sectionLabel,
253
+ itemIndex: index,
254
+ itemLabel: resolveSectionItemLabel(sectionKey, safeItem, index),
255
+ type: compareFields.type,
256
+ width: compareFields.width,
257
+ height: compareFields.height,
258
+ bgColor: compareFields.bgColor,
259
+ bgImageUrl: compareFields.bgImageUrl,
260
+ sheetLocation: compareFields.sheetLocation,
261
+ elementCount: compareFields.elementCount,
262
+ bleed: compareFields.bleed,
263
+ backend: compareFields.backend,
264
+ template: compareFields.template,
265
+ flysheetParams: compareFields.flysheetParams,
266
+ },
267
+ compareFields,
268
+ };
269
+ }
270
+ /**
271
+ * 计算 section item 的新增列表。
272
+ */
273
+ function diffAddedSectionItems(sourceMap, targetMap) {
274
+ return Object.keys(sourceMap)
275
+ .filter((key) => !targetMap[key])
276
+ .map((key) => sourceMap[key].record)
277
+ .slice(0, 100);
278
+ }
279
+ /**
280
+ * 计算 section item 的字段变化。
281
+ */
282
+ function diffChangedSectionItems(beforeMap, afterMap, maxChanges) {
283
+ const changes = [];
284
+ for (const [lookupKey, beforeEntry] of Object.entries(beforeMap)) {
285
+ const afterEntry = afterMap[lookupKey];
286
+ if (!afterEntry) {
287
+ continue;
288
+ }
289
+ const fieldsChanged = diffFlatObject(beforeEntry.compareFields, afterEntry.compareFields);
290
+ if (!fieldsChanged.length) {
291
+ continue;
292
+ }
293
+ changes.push({
294
+ lookupKey,
295
+ id: afterEntry.record.id || beforeEntry.record.id || null,
296
+ path: afterEntry.record.path,
297
+ sectionKey: afterEntry.record.sectionKey,
298
+ sectionLabel: afterEntry.record.sectionLabel,
299
+ itemLabel: afterEntry.record.itemLabel,
300
+ type: afterEntry.record.type,
301
+ fieldsChanged,
302
+ });
303
+ }
304
+ return changes.slice(0, maxChanges);
305
+ }
306
+ /**
307
+ * 计算 source 相对 target 新增的对象。
308
+ */
309
+ function diffAdded(sourceMap, targetMap) {
310
+ return Object.keys(sourceMap)
311
+ .filter((key) => !targetMap[key])
312
+ .map((key) => sourceMap[key])
313
+ .slice(0, 100);
314
+ }
315
+ /**
316
+ * 针对同一个元素,比较诊断敏感字段。
317
+ * 当前不追求覆盖所有业务字段,而是先覆盖最可能引发客诉的问题字段。
318
+ */
319
+ function diffChangedElements(beforeMap, afterMap) {
320
+ const changes = [];
321
+ for (const [lookupKey, beforeElement] of Object.entries(beforeMap)) {
322
+ const afterElement = afterMap[lookupKey];
323
+ if (!afterElement) {
324
+ continue;
325
+ }
326
+ const fieldDiffs = [
327
+ ...diffFlatObject(beforeElement.geometry, afterElement.geometry, "geometry"),
328
+ ...diffFlatObject(beforeElement.crop, afterElement.crop, "crop"),
329
+ ...diffFlatObject({
330
+ type: beforeElement.type,
331
+ pagePath: beforeElement.pagePath,
332
+ pageOrder: beforeElement.pageOrder,
333
+ imageRef: beforeElement.imageRef,
334
+ imageName: beforeElement.imageName,
335
+ }, {
336
+ type: afterElement.type,
337
+ pagePath: afterElement.pagePath,
338
+ pageOrder: afterElement.pageOrder,
339
+ imageRef: afterElement.imageRef,
340
+ imageName: afterElement.imageName,
341
+ }),
342
+ ];
343
+ if (!fieldDiffs.length) {
344
+ continue;
345
+ }
346
+ changes.push({
347
+ lookupKey,
348
+ id: afterElement.id || beforeElement.id || null,
349
+ type: afterElement.type,
350
+ pagePath: afterElement.pagePath,
351
+ fieldsChanged: fieldDiffs,
352
+ });
353
+ }
354
+ return changes;
355
+ }
356
+ /**
357
+ * 对简单对象做字段级 diff。
358
+ * prefix 用来把字段标记成 `geometry.x`、`crop.cropLUX` 这种形式,方便后续报告直接展示。
359
+ */
360
+ function diffFlatObject(beforeObject, afterObject, prefix = "") {
361
+ const keys = [...new Set([...Object.keys(beforeObject || {}), ...Object.keys(afterObject || {})])].sort();
362
+ const diffs = [];
363
+ for (const key of keys) {
364
+ const beforeValue = beforeObject ? beforeObject[key] : undefined;
365
+ const afterValue = afterObject ? afterObject[key] : undefined;
366
+ if (stableStringify(beforeValue) === stableStringify(afterValue)) {
367
+ continue;
368
+ }
369
+ diffs.push({
370
+ field: prefix ? `${prefix}.${key}` : key,
371
+ before: beforeValue === undefined ? null : beforeValue,
372
+ after: afterValue === undefined ? null : afterValue,
373
+ });
374
+ }
375
+ return diffs;
376
+ }
377
+ /**
378
+ * 生成稳定字符串,用于判断两个值是否等价。
379
+ * 这里不能直接用 JSON.stringify,因为对象 key 顺序可能不稳定。
380
+ */
381
+ function stableStringify(value) {
382
+ if (value === null || value === undefined) {
383
+ return String(value);
384
+ }
385
+ if (typeof value !== "object") {
386
+ return JSON.stringify(value);
387
+ }
388
+ if (Array.isArray(value)) {
389
+ return `[${value.map(stableStringify).join(",")}]`;
390
+ }
391
+ const keys = Object.keys(value).sort();
392
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
393
+ }
394
+ /**
395
+ * 输出快照摘要,便于快速对比 before / after 的总体规模变化。
396
+ */
397
+ function summarizeSnapshot(snapshot) {
398
+ const pages = snapshot.pages.map(enrichPageForDiff);
399
+ return {
400
+ projectMeta: snapshot.projectMeta,
401
+ imageCount: snapshot.imageCount,
402
+ pageCount: snapshot.pageCount,
403
+ elementCount: snapshot.elementCount,
404
+ pageCollectionCounts: snapshot.pageCollectionCounts,
405
+ logicalPageCollectionCounts: countBy(pages, (page) => page.logicalCollectionName),
406
+ elementTypeCounts: snapshot.elementTypeCounts,
407
+ };
408
+ }
409
+ /**
410
+ * 给页面补上对业务更友好的“区域标签”。
411
+ *
412
+ * 例如:
413
+ * - `project.pages[0]` -> 内页
414
+ * - `project.cover.containers[1]` -> 封面 / 书脊
415
+ * - `project.edge[0]` -> 书侧边 / 顶部侧边
416
+ */
417
+ function enrichPageForDiff(page) {
418
+ const logicalCollectionName = resolvePageSectionName(page.path, page.collectionName);
419
+ return {
420
+ ...page,
421
+ logicalCollectionName,
422
+ logicalCollectionLabel: PAGE_SECTION_LABELS[logicalCollectionName] || page.collectionName || "unknown",
423
+ logicalItemLabel: resolveSectionItemLabel(logicalCollectionName, page, page.collectionIndex),
424
+ };
425
+ }
426
+ /**
427
+ * 从页面路径推导它属于哪个页面区域。
428
+ */
429
+ function resolvePageSectionName(pagePath, fallbackCollectionName = "") {
430
+ if (pagePath.startsWith("project.cover")) {
431
+ return "cover";
432
+ }
433
+ if (pagePath.startsWith("project.frontFlysheet[")) {
434
+ return "frontFlysheet";
435
+ }
436
+ if (pagePath.startsWith("project.backFlysheet[")) {
437
+ return "backFlysheet";
438
+ }
439
+ if (pagePath.startsWith("project.edge[")) {
440
+ return "edge";
441
+ }
442
+ if (pagePath.startsWith("project.pages[")) {
443
+ return "pages";
444
+ }
445
+ return fallbackCollectionName || "unknown";
446
+ }
447
+ /**
448
+ * 把不同 section 下的 item 映射成稳定、可读的标签。
449
+ */
450
+ function resolveSectionItemLabel(sectionKey, item, index) {
451
+ switch (sectionKey) {
452
+ case "cover":
453
+ return resolveCoverItemLabel(item.type, index);
454
+ case "frontFlysheet":
455
+ return resolveFlysheetItemLabel("前扉页", item.type, index);
456
+ case "backFlysheet":
457
+ return resolveFlysheetItemLabel("后扉页", item.type, index);
458
+ case "edge":
459
+ return resolveEdgeItemLabel(item.type, index);
460
+ case "pages":
461
+ return Number.isInteger(index) ? `第 ${index + 1} 页` : "内页";
462
+ default:
463
+ return item.type || null;
464
+ }
465
+ }
466
+ function resolveCoverItemLabel(type, index) {
467
+ if (type === "Full") {
468
+ return "封面展开面";
469
+ }
470
+ if (type === "Spine") {
471
+ return "书脊";
472
+ }
473
+ return Number.isInteger(index) ? `封面区域 ${index + 1}` : "封面";
474
+ }
475
+ function resolveFlysheetItemLabel(sectionLabel, type, index) {
476
+ if (type === "leftPage") {
477
+ return `${sectionLabel}-左页`;
478
+ }
479
+ if (type === "rightPage") {
480
+ return `${sectionLabel}-右页`;
481
+ }
482
+ return Number.isInteger(index) ? `${sectionLabel}-${index + 1}` : sectionLabel;
483
+ }
484
+ function resolveEdgeItemLabel(type, index) {
485
+ if (type === "headEdge") {
486
+ return "顶部侧边";
487
+ }
488
+ if (type === "foreEdge") {
489
+ return "外侧边";
490
+ }
491
+ if (type === "tailEdge") {
492
+ return "底部侧边";
493
+ }
494
+ return Number.isInteger(index) ? `侧边面 ${index + 1}` : PAGE_SECTION_LABELS.edge;
495
+ }
496
+ /**
497
+ * 统一拿到 project 根节点,兼容:
498
+ * - { project: {...}, images: [...] }
499
+ * - 直接传 {...projectFields}
500
+ */
501
+ function getProjectRoot(rawSnapshot) {
502
+ if (!rawSnapshot || typeof rawSnapshot !== "object") {
503
+ return {};
504
+ }
505
+ const snapshot = rawSnapshot;
506
+ return snapshot.project && typeof snapshot.project === "object" ? snapshot.project : snapshot;
507
+ }
508
+ function hasOwn(source, key) {
509
+ return Boolean(source) && Object.prototype.hasOwnProperty.call(source, key);
510
+ }
511
+ function toArray(value) {
512
+ return Array.isArray(value) ? value : [];
513
+ }
514
+ function getFieldValue(source, key) {
515
+ return hasOwn(source, key) ? source[key] : null;
516
+ }
517
+ function cloneObject(value) {
518
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
519
+ return null;
520
+ }
521
+ return { ...value };
522
+ }
523
+ function countBy(items, iteratee) {
524
+ const counts = {};
525
+ for (const item of items) {
526
+ const key = iteratee(item) || "unknown";
527
+ counts[key] = (counts[key] || 0) + 1;
528
+ }
529
+ return counts;
530
+ }
531
+ //# sourceMappingURL=project-diff.js.map
@@ -0,0 +1,38 @@
1
+ export * from "./types.js";
2
+ export { parseApmProviderConfig } from "./apm/config.js";
3
+ export type { ApmProviderConfig } from "./apm/config.js";
4
+ export { createApmConfigError, ERROR_EMPTY, ERROR_MISSING, HINT, isApmConfigured, SENTENCE, } from "./apm/config-state.js";
5
+ export type { ApmProviderState } from "./apm/config-state.js";
6
+ export { createApmClient } from "./apm/client.js";
7
+ export type { ApmClientDeps, ApmClient, ApmQueryParams, ApmQueryResult, FlushResult, FlushRecord, } from "./apm/client.js";
8
+ export { ApmRequestError } from "./apm/errors.js";
9
+ export type { ApmErrorKind, ApmRequestErrorOptions } from "./apm/errors.js";
10
+ export { fetchLiveProject, resolveUserId } from "./apm/portal.js";
11
+ export type { ApmPortalDeps, FetchLiveProjectParams, LiveProjectResult, ResolveUserIdParams, } from "./apm/portal.js";
12
+ export { assertColumnName, assertSafeValue, FIXED_REQUEST_KEYS, isSiteHost, LOG_TYPES, normalizeLogTime, ORDERS, } from "./apm/values.js";
13
+ export type { ApmOrder, LogType } from "./apm/values.js";
14
+ export { loadConfig, machineConfigDir } from "./config.js";
15
+ export type { ConfigSource, LoadConfigOptions } from "./config.js";
16
+ export { loadEventSemanticsTable } from "./semantics/table-loader.js";
17
+ export type { EventSemanticsTable } from "./semantics/table-loader.js";
18
+ export * from "./normalize/log.js";
19
+ export * from "./normalize/project.js";
20
+ export * from "./semantics/resolver.js";
21
+ export { CASE_CONFLICT_REGISTRY, WHITESPACE_KEY_ALLOWLIST, collectTopLevelKeys, validateEventSemanticsTable, } from "./semantics/table-schema.js";
22
+ export { runDiagnosticRules } from "./rules/engine.js";
23
+ export { diffProjectSnapshots } from "./diff/project-diff.js";
24
+ export type { ProjectDiffResult } from "./diff/project-diff.js";
25
+ export { createFileLogProvider, createLogProvider } from "./providers/log-provider.js";
26
+ export type { LogFetchResult, LogProvenance, LogProvider } from "./providers/log-provider.js";
27
+ export { createProjectProvider } from "./providers/project-provider.js";
28
+ export type { ProjectProvider, ProjectSnapshotVersion } from "./providers/project-provider.js";
29
+ export { createRemoteProjectProvider } from "./providers/remote-project-provider.js";
30
+ export type { ProjectPullEntry, RemoteProjectProvider, RemoteProjectPullResult, } from "./providers/remote-project-provider.js";
31
+ export { createSourceProvider } from "./providers/source-provider.js";
32
+ export type { SourceProvider } from "./providers/source-provider.js";
33
+ export { appendTrace, createRunDir, loadRun, readArtifact, writeArtifact, writeStageStatus, } from "./run/store.js";
34
+ export type { RunHandle, RunInput, StageName, TraceEntry } from "./run/store.js";
35
+ export { diagnosisResultSchema, validateDiagnosisResult } from "./run/result-schema.js";
36
+ export type { DiagnosisResult } from "./run/result-schema.js";
37
+ export { createMysqlClient } from "./db/client.js";
38
+ export type { DbClient, DbParam, DbRow, MysqlClientConfig } from "./db/client.js";
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ export * from "./types.js";
2
+ export { parseApmProviderConfig } from "./apm/config.js";
3
+ export { createApmConfigError, ERROR_EMPTY, ERROR_MISSING, HINT, isApmConfigured, SENTENCE, } from "./apm/config-state.js";
4
+ export { createApmClient } from "./apm/client.js";
5
+ export { ApmRequestError } from "./apm/errors.js";
6
+ export { fetchLiveProject, resolveUserId } from "./apm/portal.js";
7
+ export { assertColumnName, assertSafeValue, FIXED_REQUEST_KEYS, isSiteHost, LOG_TYPES, normalizeLogTime, ORDERS, } from "./apm/values.js";
8
+ export { loadConfig, machineConfigDir } from "./config.js";
9
+ export { loadEventSemanticsTable } from "./semantics/table-loader.js";
10
+ export * from "./normalize/log.js";
11
+ export * from "./normalize/project.js";
12
+ export * from "./semantics/resolver.js";
13
+ export { CASE_CONFLICT_REGISTRY, WHITESPACE_KEY_ALLOWLIST, collectTopLevelKeys, validateEventSemanticsTable, } from "./semantics/table-schema.js";
14
+ export { runDiagnosticRules } from "./rules/engine.js";
15
+ export { diffProjectSnapshots } from "./diff/project-diff.js";
16
+ export { createFileLogProvider, createLogProvider } from "./providers/log-provider.js";
17
+ export { createProjectProvider } from "./providers/project-provider.js";
18
+ export { createRemoteProjectProvider } from "./providers/remote-project-provider.js";
19
+ export { createSourceProvider } from "./providers/source-provider.js";
20
+ export { appendTrace, createRunDir, loadRun, readArtifact, writeArtifact, writeStageStatus, } from "./run/store.js";
21
+ export { diagnosisResultSchema, validateDiagnosisResult } from "./run/result-schema.js";
22
+ export { createMysqlClient } from "./db/client.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ import type { LogEnvelope } from "../types.js";
2
+ /**
3
+ * 统一把各种日志返回结构整理成一个标准格式。
4
+ *
5
+ * 当前兼容三种输入:
6
+ * - 直接是数组
7
+ * - `{ data: { result: [...] } }`
8
+ * - `{ result: [...] }`
9
+ *
10
+ * 这样无论是线上 APM 原始返回,还是离线导出的局部样本,都能直接喂进分析器。
11
+ */
12
+ export declare function normalizeLogEnvelope(raw: unknown, sourcePath?: string): LogEnvelope;