@restoai/resto-datacli 0.1.12 → 0.2.1

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.
@@ -1,9 +1,10 @@
1
1
  import { Command } from "commander";
2
2
  import { AuthStore } from "../config/auth-store.js";
3
3
  import { LocalKnowledgeStore } from "../knowledge/local-store.js";
4
- import { ManifestClient } from "../knowledge/manifest-client.js";
4
+ import { KnowledgeManifestError, ManifestClient } from "../knowledge/manifest-client.js";
5
5
  import { KnowledgeSyncService } from "../knowledge/sync-service.js";
6
6
  import { failJson, printJson } from "./json-command.js";
7
+ const DEFAULT_REPORT_CLI_SERVICE_URL = "http://127.0.0.1:18081";
7
8
  function parseSchemaVersion(write, value) {
8
9
  const parsed = Number.parseInt(value, 10);
9
10
  if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== value) {
@@ -23,8 +24,14 @@ function createDefaultSyncService(localRoot, profile, baseUrl, factory) {
23
24
  })
24
25
  });
25
26
  }
26
- function resolveManifestBaseUrl(profile) {
27
- return profile.baseUrl;
27
+ function resolveManifestBaseUrl(reportCliUrl) {
28
+ return reportCliUrl || process.env.RESTO_REPORT_CLI_URL || DEFAULT_REPORT_CLI_SERVICE_URL;
29
+ }
30
+ function failKnowledgeError(write, error) {
31
+ if (error instanceof KnowledgeManifestError) {
32
+ failJson(write, error.code, error.message);
33
+ }
34
+ failJson(write, "KNOWLEDGE_ERROR", "knowledge command failed");
28
35
  }
29
36
  export function createKnowledgeCommand(options = {}) {
30
37
  const write = options.write ?? ((message) => process.stdout.write(message));
@@ -44,6 +51,7 @@ export function createKnowledgeCommand(options = {}) {
44
51
  .description("Sync local knowledge artifacts")
45
52
  .option("--env <env>", "Knowledge environment", "test")
46
53
  .option("--schema-version <version>", "Knowledge schema version", "1")
54
+ .option("--report-cli-url <url>", "report-cli-service base URL for knowledge manifest", process.env.RESTO_REPORT_CLI_URL ?? DEFAULT_REPORT_CLI_SERVICE_URL)
47
55
  .option("--fixture", "Sync bundled local fixture knowledge without remote services or auth")
48
56
  .allowExcessArguments(false)
49
57
  .action(async (rawOptions) => {
@@ -57,18 +65,24 @@ export function createKnowledgeCommand(options = {}) {
57
65
  if (!profile) {
58
66
  failJson(write, "AUTH_NOT_CONFIGURED", "Authentication profile is not configured. Run resto auth login first.");
59
67
  }
60
- return createDefaultSyncService(options.localRoot, profile, resolveManifestBaseUrl(profile), options.createKnowledgeSyncService);
68
+ return createDefaultSyncService(options.localRoot, profile, resolveManifestBaseUrl(rawOptions.reportCliUrl), options.createKnowledgeSyncService);
61
69
  })();
62
- printJson(write, await service.sync({
63
- env: rawOptions.env,
64
- schemaVersion
65
- }));
70
+ try {
71
+ printJson(write, await service.sync({
72
+ env: rawOptions.env,
73
+ schemaVersion
74
+ }));
75
+ }
76
+ catch (error) {
77
+ failKnowledgeError(write, error);
78
+ }
66
79
  });
67
80
  knowledge
68
81
  .command("check")
69
82
  .description("Check whether local knowledge is stale against the remote manifest")
70
83
  .option("--env <env>", "Knowledge environment", "test")
71
84
  .option("--schema-version <version>", "Knowledge schema version", "1")
85
+ .option("--report-cli-url <url>", "report-cli-service base URL for knowledge manifest", process.env.RESTO_REPORT_CLI_URL ?? DEFAULT_REPORT_CLI_SERVICE_URL)
72
86
  .option("--fixture", "Check bundled local fixture knowledge without remote services or auth")
73
87
  .allowExcessArguments(false)
74
88
  .action(async (rawOptions) => {
@@ -82,12 +96,17 @@ export function createKnowledgeCommand(options = {}) {
82
96
  if (!profile) {
83
97
  failJson(write, "AUTH_NOT_CONFIGURED", "Authentication profile is not configured. Run resto auth login first.");
84
98
  }
85
- return createDefaultSyncService(options.localRoot, profile, resolveManifestBaseUrl(profile), options.createKnowledgeSyncService);
99
+ return createDefaultSyncService(options.localRoot, profile, resolveManifestBaseUrl(rawOptions.reportCliUrl), options.createKnowledgeSyncService);
86
100
  })();
87
- printJson(write, await service.check({
88
- env: rawOptions.env,
89
- schemaVersion
90
- }));
101
+ try {
102
+ printJson(write, await service.check({
103
+ env: rawOptions.env,
104
+ schemaVersion
105
+ }));
106
+ }
107
+ catch (error) {
108
+ failKnowledgeError(write, error);
109
+ }
91
110
  });
92
111
  return knowledge;
93
112
  }
@@ -0,0 +1,109 @@
1
+ import { Command } from "commander";
2
+ import { AuthStore } from "../config/auth-store.js";
3
+ import { ReportApiError, ReportCliServiceClient } from "../report-api/report-api-client.js";
4
+ import { configureJsonParseErrors, failJson, parsePositiveIntegerArgument, printJson } from "./json-command.js";
5
+ const DEFAULT_REPORT_CLI_SERVICE_URL = "http://127.0.0.1:18081";
6
+ const MAX_POS_ORDER_BUSINESS_DATE_RANGE_DAYS = 31;
7
+ function requiredOption(write, value, optionName) {
8
+ if (!value) {
9
+ failJson(write, "INVALID_ARGUMENTS", `Missing required option: ${optionName}`);
10
+ }
11
+ return value;
12
+ }
13
+ function resolveReportCliUrl(options, auth, explicitUrl) {
14
+ return explicitUrl
15
+ ?? options.reportCliUrl
16
+ ?? process.env.RESTO_REPORT_CLI_URL
17
+ ?? auth.baseUrl
18
+ ?? DEFAULT_REPORT_CLI_SERVICE_URL;
19
+ }
20
+ function safeErrorMessage(error, token) {
21
+ const message = error instanceof ReportApiError || error instanceof Error
22
+ ? error.message
23
+ : "report-cli-service order detail request failed";
24
+ return message.split(token).join("***").replace(/\s+/g, " ").trim().slice(0, 240);
25
+ }
26
+ function parseBusinessDate(write, value, optionName) {
27
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
28
+ failJson(write, "INVALID_ARGUMENTS", `${optionName} must use YYYY-MM-DD.`);
29
+ }
30
+ const [year, month, day] = value.split("-").map(Number);
31
+ const date = new Date(Date.UTC(year, month - 1, day));
32
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
33
+ failJson(write, "INVALID_ARGUMENTS", `${optionName} must be a valid calendar date.`);
34
+ }
35
+ return date;
36
+ }
37
+ function resolvePosOrderBusinessDateRange(write, rawOptions) {
38
+ const businessDateStart = requiredOption(write, rawOptions.businessDateStart?.trim(), "--business-date-start");
39
+ const businessDateEnd = requiredOption(write, rawOptions.businessDateEnd?.trim(), "--business-date-end");
40
+ const start = parseBusinessDate(write, businessDateStart, "--business-date-start");
41
+ const end = parseBusinessDate(write, businessDateEnd, "--business-date-end");
42
+ if (start > end) {
43
+ failJson(write, "INVALID_ARGUMENTS", "--business-date-start must not be after --business-date-end.");
44
+ }
45
+ const rangeDays = Math.floor((end.getTime() - start.getTime()) / 86_400_000) + 1;
46
+ if (rangeDays > MAX_POS_ORDER_BUSINESS_DATE_RANGE_DAYS) {
47
+ failJson(write, "INVALID_ARGUMENTS", "POS order business date range must not exceed 31 days.");
48
+ }
49
+ return { businessDateStart, businessDateEnd };
50
+ }
51
+ export function createOrderCommand(options = {}) {
52
+ const write = options.write ?? ((message) => process.stdout.write(message));
53
+ const order = new Command("order").description("Query protected order drilldown details");
54
+ order
55
+ .command("detail")
56
+ .description("Get rendered order detail by POS order id or internal order id")
57
+ .requiredOption("--report-id <id>", "Authorized main report id, for example 888002")
58
+ .option("--pos-order-id <id>", "POS order id")
59
+ .option("--order-id <id>", "Internal order id")
60
+ .option("--business-date-start <date>", "Business date start for POS order id (YYYY-MM-DD)")
61
+ .option("--business-date-end <date>", "Business date end for POS order id (YYYY-MM-DD)")
62
+ .option("--report-cli-url <url>", "report-cli-service gateway base URL")
63
+ .allowExcessArguments(false)
64
+ .action(async (rawOptions) => {
65
+ const auth = new AuthStore(options.localRoot).load();
66
+ if (!auth) {
67
+ failJson(write, "AUTH_NOT_CONFIGURED", "No local auth profile found. Run resto auth login first.");
68
+ }
69
+ const reportId = parsePositiveIntegerArgument(write, "--report-id", requiredOption(write, rawOptions.reportId, "--report-id"));
70
+ const posOrderId = rawOptions.posOrderId?.trim();
71
+ const orderId = rawOptions.orderId?.trim();
72
+ if (Boolean(posOrderId) === Boolean(orderId)) {
73
+ failJson(write, "INVALID_ARGUMENTS", "Pass exactly one of --pos-order-id and --order-id.");
74
+ }
75
+ const businessDateRange = posOrderId
76
+ ? resolvePosOrderBusinessDateRange(write, rawOptions)
77
+ : undefined;
78
+ if (!posOrderId && (rawOptions.businessDateStart || rawOptions.businessDateEnd)) {
79
+ failJson(write, "INVALID_ARGUMENTS", "Business date options are only supported with --pos-order-id.");
80
+ }
81
+ const request = {
82
+ reportId,
83
+ ...(posOrderId ? { posOrderId } : {}),
84
+ ...(orderId ? { orderId } : {}),
85
+ ...(businessDateRange ?? {})
86
+ };
87
+ const baseUrl = resolveReportCliUrl(options, auth, rawOptions.reportCliUrl);
88
+ const client = options.orderDetailClient
89
+ ?? options.createOrderDetailClient?.(auth, baseUrl)
90
+ ?? new ReportCliServiceClient({ auth, baseUrl });
91
+ let response;
92
+ try {
93
+ response = await client.getReportOrderDetail(request);
94
+ }
95
+ catch (error) {
96
+ failJson(write, "REPORT_API_ERROR", safeErrorMessage(error, auth.token));
97
+ }
98
+ printJson(write, {
99
+ source: "/api/report-cli/report/order/detail",
100
+ reportId: response.reportId,
101
+ shopId: response.shopId,
102
+ posOrderId: response.posOrderId,
103
+ orderId: response.orderId,
104
+ detail: response.detail
105
+ });
106
+ });
107
+ configureJsonParseErrors(order, write);
108
+ return order;
109
+ }
@@ -46,6 +46,36 @@ function toPublicReportCompositionRecord(composition) {
46
46
  source: composition.source
47
47
  };
48
48
  }
49
+ function toPublicReportMetricsByDimRecord(record) {
50
+ return {
51
+ reportId: record.reportId,
52
+ sourceReportId: record.sourceReportId,
53
+ modelId: record.modelId,
54
+ dimensionFieldName: record.dimensionFieldName,
55
+ dimensionDisplayText: record.dimensionDisplayText,
56
+ onlyMetricsByDim: record.onlyMetricsByDim,
57
+ disableMetricsByDim: record.disableMetricsByDim,
58
+ metricFieldNames: record.metricFieldNames,
59
+ defaultMetricFieldNames: record.defaultMetricFieldNames,
60
+ source: record.source
61
+ };
62
+ }
63
+ function toPublicReportMergedHeadRecord(record) {
64
+ return {
65
+ reportId: record.reportId,
66
+ sourceReportId: record.sourceReportId,
67
+ modelId: record.modelId,
68
+ mergedHeadId: record.mergedHeadId,
69
+ defaultSortIndex: record.defaultSortIndex,
70
+ primaryHeadMetricsCode: record.primaryHeadMetricsCode,
71
+ subHeadIsDynamic: record.subHeadIsDynamic,
72
+ subHeadMetricsCodes: record.subHeadMetricsCodes,
73
+ subHeadByDimCode: record.subHeadByDimCode,
74
+ displaySubTotal: record.displaySubTotal,
75
+ subTotalMetricsCode: record.subTotalMetricsCode,
76
+ source: record.source
77
+ };
78
+ }
49
79
  function toPublicReportQueryPlanRecord(plan) {
50
80
  return {
51
81
  reportId: plan.reportId,
@@ -100,6 +130,8 @@ export function toPublicReportRecord(report) {
100
130
  ...(report.routeMap ? { routeMap: report.routeMap.map(toPublicReportRouteMapRecord) } : {}),
101
131
  ...(report.menuTree ? { menuTree: report.menuTree.map(toPublicReportMenuTreeRecord) } : {}),
102
132
  ...(report.composition ? { composition: toPublicReportCompositionRecord(report.composition) } : {}),
133
+ ...(report.metricsByDim ? { metricsByDim: report.metricsByDim.map(toPublicReportMetricsByDimRecord) } : {}),
134
+ ...(report.mergedHeads ? { mergedHeads: report.mergedHeads.map(toPublicReportMergedHeadRecord) } : {}),
103
135
  ...(report.queryPlans ? { queryPlans: report.queryPlans.map(toPublicReportQueryPlanRecord) } : {}),
104
136
  ...(report.filterSurfaces ? { filterSurfaces: report.filterSurfaces.map(toPublicReportFilterSurfaceRecord) } : {}),
105
137
  ...(report.entityBindings ? { entityBindings: report.entityBindings.map(toPublicReportEntityBindingRecord) } : {})
@@ -2,10 +2,11 @@ import { readFileSync } from "node:fs";
2
2
  import { Command } from "commander";
3
3
  import { AuthStore } from "../config/auth-store.js";
4
4
  import { SearchIndex } from "../knowledge/search-index.js";
5
- import { ReportApiClient, ReportApiError } from "../report-api/report-api-client.js";
5
+ import { ReportApiError, ReportCliServiceClient } from "../report-api/report-api-client.js";
6
6
  import { normalizeExecutableQueryPlan, parseQueryPlan, QueryPlanValidator } from "../report-api/query-plan.js";
7
7
  import { SnapshotStore } from "../snapshot/snapshot-store.js";
8
8
  import { configureJsonParseErrors, failJson, failMissingKnowledge, JsonCommandError, jsonError, printJson } from "./json-command.js";
9
+ const DEFAULT_REPORT_CLI_SERVICE_URL = "http://127.0.0.1:18081";
9
10
  function parseSchemaVersion(write, value) {
10
11
  const parsed = Number.parseInt(value, 10);
11
12
  if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== value) {
@@ -57,9 +58,23 @@ function throwValidationError(write, validation) {
57
58
  function failReportApiError(write, error) {
58
59
  const message = error instanceof ReportApiError
59
60
  ? error.message
60
- : "Report API request failed";
61
+ : "report-cli-service request failed";
61
62
  failJson(write, "REPORT_API_ERROR", message);
62
63
  }
64
+ function parseQueryBackend(write, value) {
65
+ const backend = value || "report-cli-service";
66
+ if (backend === "report-cli-service") {
67
+ return backend;
68
+ }
69
+ if (backend === "report-api") {
70
+ failJson(write, "INVALID_ARGUMENTS", "Direct report-api query backend is not allowed. Use report-cli-service.");
71
+ }
72
+ failJson(write, "INVALID_ARGUMENTS", "Invalid query backend. Expected report-cli-service.");
73
+ }
74
+ function createQueryDataClient(options, auth, reportCliUrl) {
75
+ const baseUrl = reportCliUrl || DEFAULT_REPORT_CLI_SERVICE_URL;
76
+ return options.reportCliServiceClient ?? options.createReportCliServiceClient?.(auth, baseUrl) ?? new ReportCliServiceClient({ auth, baseUrl });
77
+ }
63
78
  function schemaFromValidation(validation, response) {
64
79
  if (response.schema.length > 0) {
65
80
  return response.schema;
@@ -95,7 +110,7 @@ Examples:
95
110
  resto query validate --plan ./plan.json
96
111
  resto query execute --plan ./plan.json
97
112
 
98
- Field names inside plans must use exact report field codes required by report-api.
113
+ Field names inside plans must use exact report field codes required by the report service.
99
114
  `);
100
115
  query
101
116
  .command("validate")
@@ -115,6 +130,8 @@ Field names inside plans must use exact report field codes required by report-ap
115
130
  .option("--plan <path>", "Query plan JSON file")
116
131
  .option("--env <env>", "Knowledge environment", options.env ?? "test")
117
132
  .option("--schema-version <version>", "Knowledge schema version", "1")
133
+ .option("--backend <backend>", "Execution backend: report-cli-service", process.env.RESTO_QUERY_BACKEND ?? "report-cli-service")
134
+ .option("--report-cli-url <url>", "report-cli-service base URL for query execution", process.env.RESTO_REPORT_CLI_URL ?? DEFAULT_REPORT_CLI_SERVICE_URL)
118
135
  .allowExcessArguments(false)
119
136
  .action(async (rawOptions) => {
120
137
  const plan = readPlanFile(requireOption(write, rawOptions.plan, "--plan"), write);
@@ -128,7 +145,8 @@ Field names inside plans must use exact report field codes required by report-ap
128
145
  if (!auth) {
129
146
  failJson(write, "AUTH_NOT_CONFIGURED", "No local auth profile found. Run resto auth login first.");
130
147
  }
131
- const client = options.reportApiClient ?? options.createReportApiClient?.(auth) ?? new ReportApiClient({ auth });
148
+ parseQueryBackend(write, rawOptions.backend);
149
+ const client = createQueryDataClient(options, auth, rawOptions.reportCliUrl);
132
150
  let response;
133
151
  try {
134
152
  response = await client.queryData(parsedPlan, { env: rawOptions.env, schemaVersion });
@@ -1,7 +1,8 @@
1
1
  import { Command } from "commander";
2
2
  import { AuthStore } from "../config/auth-store.js";
3
- import { ReportApiClient, ReportApiError } from "../report-api/report-api-client.js";
3
+ import { ReportApiError, ReportCliServiceClient } from "../report-api/report-api-client.js";
4
4
  import { configureJsonParseErrors, failJson, parsePositiveIntegerArgument, printJson } from "./json-command.js";
5
+ const DEFAULT_REPORT_CLI_SERVICE_URL = "http://127.0.0.1:18081";
5
6
  function requireOption(write, value, optionName) {
6
7
  if (!value) {
7
8
  failJson(write, "INVALID_ARGUMENTS", `Missing required option: ${optionName}. Pass it explicitly or save report context with auth login.`);
@@ -47,23 +48,76 @@ function filterShops(response, query, limit) {
47
48
  .sort((left, right) => left.score - right.score || left.index - right.index);
48
49
  return scored.slice(0, limit).map((item) => item.shop);
49
50
  }
50
- function failReportApiError(write, error) {
51
+ function stringValue(value) {
52
+ if (typeof value === "string") {
53
+ return value;
54
+ }
55
+ if (typeof value === "number" && Number.isFinite(value)) {
56
+ return String(value);
57
+ }
58
+ return undefined;
59
+ }
60
+ function redactSensitiveText(value, sensitiveValues) {
61
+ return sensitiveValues.reduce((result, sensitiveValue) => {
62
+ if (!sensitiveValue) {
63
+ return result;
64
+ }
65
+ return result.split(sensitiveValue).join("***");
66
+ }, value);
67
+ }
68
+ function safeErrorDetail(error, sensitiveValues) {
69
+ const details = [];
70
+ collectErrorDetails(error, details, new Set());
71
+ const detail = details.join(": ").replace(/\s+/g, " ").trim();
72
+ return detail ? redactSensitiveText(detail, sensitiveValues).slice(0, 240) : undefined;
73
+ }
74
+ function collectErrorDetails(error, details, seen) {
75
+ if (error === null || error === undefined || seen.has(error)) {
76
+ return;
77
+ }
78
+ seen.add(error);
79
+ if (error instanceof Error && error.message) {
80
+ details.push(error.message);
81
+ }
82
+ if (typeof error !== "object") {
83
+ return;
84
+ }
85
+ const record = error;
86
+ const causeParts = [record.code, record.syscall, record.address, record.port].flatMap((value) => {
87
+ const text = stringValue(value);
88
+ return text ? [text] : [];
89
+ });
90
+ if (causeParts.length > 0) {
91
+ details.push(causeParts.join(" "));
92
+ }
93
+ collectErrorDetails(record.cause, details, seen);
94
+ if (Array.isArray(record.errors)) {
95
+ for (const nestedError of record.errors.slice(0, 3)) {
96
+ collectErrorDetails(nestedError, details, seen);
97
+ }
98
+ }
99
+ }
100
+ function failReportServiceError(write, error, sensitiveValues = []) {
51
101
  const status = error instanceof ReportApiError ? error.status : undefined;
52
- const message = status === undefined ? "Report API request failed" : `Report API request failed with status ${status}`;
53
- failJson(write, "REPORT_API_ERROR", message);
102
+ const message = status === undefined ? "report-cli-service request failed" : `report-cli-service request failed with status ${status}`;
103
+ const detail = safeErrorDetail(error, sensitiveValues);
104
+ failJson(write, "REPORT_API_ERROR", `${message}${detail ? `: ${detail}` : ""}`);
105
+ }
106
+ function createShopFiltersClient(options, auth, reportCliUrl) {
107
+ const baseUrl = reportCliUrl || options.reportCliUrl || DEFAULT_REPORT_CLI_SERVICE_URL;
108
+ return options.shopFiltersClient ?? options.createShopFiltersClient?.(auth, baseUrl) ?? new ReportCliServiceClient({ auth, baseUrl });
54
109
  }
55
110
  export function createShopCommand(options = {}) {
56
111
  const write = options.write ?? ((message) => process.stdout.write(message));
57
- const shop = new Command("shop").description("Search authorized report shop filters from report-api");
112
+ const shop = new Command("shop").description("Search authorized report shop filters from report-cli-service");
58
113
  shop
59
114
  .command("search")
60
115
  .description("Search online authorized shops by name or ID")
61
116
  .argument("<query...>", "Shop name or ID")
62
- .option("--employee-code <code>", "Employee code for permission filtering")
63
- .option("--organization-id <id>", "Organization ID for permission filtering")
64
117
  .option("--org-type-list <list>", "Comma-separated organization type list")
65
- .option("--organization-type <type>", "Organization-Type request header")
66
- .option("--report-id <id>", "Report ID used by report-api shop filtering")
118
+ .option("--organization-type <type>", "Organization type used by report permission context")
119
+ .option("--report-id <id>", "Report ID used by report-cli-service shop filtering")
120
+ .option("--report-cli-url <url>", "report-cli-service base URL for shop filters", process.env.RESTO_REPORT_CLI_URL ?? options.reportCliUrl ?? DEFAULT_REPORT_CLI_SERVICE_URL)
67
121
  .option("--limit <n>", "Maximum number of matches", "20")
68
122
  .allowExcessArguments(false)
69
123
  .action(async (queryParts, rawOptions) => {
@@ -75,26 +129,24 @@ export function createShopCommand(options = {}) {
75
129
  }
76
130
  const savedContext = auth.reportContext;
77
131
  const request = {
78
- employeeCode: requireOption(write, rawOptions.employeeCode ?? savedContext?.employeeCode, "--employee-code"),
79
- organizationId: requireOption(write, rawOptions.organizationId ?? savedContext?.organizationId, "--organization-id"),
80
132
  orgTypeList: rawOptions.orgTypeList
81
133
  ? parseIntegerList(write, "--org-type-list", rawOptions.orgTypeList)
82
134
  : requireOption(write, savedContext?.orgTypeList, "--org-type-list"),
83
135
  organizationType: requireOption(write, rawOptions.organizationType ?? savedContext?.organizationType, "--organization-type"),
84
136
  ...(rawOptions.reportId ? { reportId: parsePositiveIntegerArgument(write, "--report-id", rawOptions.reportId) } : {})
85
137
  };
86
- const client = options.shopApiClient ?? options.createShopApiClient?.(auth) ?? new ReportApiClient({ auth });
138
+ const client = createShopFiltersClient(options, auth, rawOptions.reportCliUrl);
87
139
  let response;
88
140
  try {
89
141
  response = await client.getReportShopFilters(request);
90
142
  }
91
143
  catch (error) {
92
- failReportApiError(write, error);
144
+ failReportServiceError(write, error, [auth.token]);
93
145
  }
94
146
  const matches = filterShops(response, query, limit);
95
147
  printJson(write, {
96
148
  query,
97
- source: "/api/report/merchant/getReportShopFilters",
149
+ source: "/api/report-cli/shop/filters",
98
150
  ...(request.reportId === undefined ? {} : { reportId: request.reportId }),
99
151
  shopFilterType: response.shopFilterType,
100
152
  totalAuthorizedShops: response.shops.length,
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  const authProfileSchema = z.object({
3
3
  baseUrl: z.string().min(1),
4
+ plat: z.string().min(1).optional(),
4
5
  token: z.string().min(1),
5
6
  corporationId: z.string().min(1),
6
7
  languageCode: z.string().min(1),
@@ -16,6 +16,8 @@ const artifactFileNames = {
16
16
  reportMenuTree: "report-menu-tree.jsonl",
17
17
  reportCompositions: "report-compositions.jsonl",
18
18
  reportMergedFields: "report-merged-fields.jsonl",
19
+ reportMetricsByDim: "report-metrics-by-dim.jsonl",
20
+ reportMergedHeads: "report-merged-heads.jsonl",
19
21
  reportQueryPlans: "report-query-plans.jsonl",
20
22
  reportFilterSurfaces: "report-filter-surfaces.jsonl",
21
23
  reportEntityBindings: "report-entity-bindings.jsonl",
@@ -1,5 +1,52 @@
1
1
  import { fetch } from "undici";
2
2
  import { parseKnowledgeManifest } from "./schema.js";
3
+ export class KnowledgeManifestError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "KnowledgeManifestError";
8
+ this.code = code;
9
+ }
10
+ }
11
+ function stringValue(value) {
12
+ if (typeof value === "string") {
13
+ return value;
14
+ }
15
+ if (typeof value === "number" && Number.isFinite(value)) {
16
+ return String(value);
17
+ }
18
+ return undefined;
19
+ }
20
+ function redactSensitiveText(value, sensitiveValues) {
21
+ return sensitiveValues.reduce((result, sensitiveValue) => {
22
+ if (!sensitiveValue) {
23
+ return result;
24
+ }
25
+ return result.split(sensitiveValue).join("***");
26
+ }, value);
27
+ }
28
+ function sanitizeRemoteMessage(value, sensitiveValues) {
29
+ const message = stringValue(value);
30
+ if (!message) {
31
+ return undefined;
32
+ }
33
+ return redactSensitiveText(message.replace(/\s+/g, " ").trim(), sensitiveValues).slice(0, 240);
34
+ }
35
+ function throwIfBusinessError(payload, sensitiveValues) {
36
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
37
+ return;
38
+ }
39
+ const body = payload;
40
+ const success = typeof body.success === "boolean" ? body.success : undefined;
41
+ const code = stringValue(body.errorCode ?? body.code);
42
+ const okCode = !code || code === "0" || code === "000";
43
+ if (okCode && success !== false) {
44
+ return;
45
+ }
46
+ const safeCode = code || "KNOWLEDGE_MANIFEST_ERROR";
47
+ const message = sanitizeRemoteMessage(body.message ?? body.msg ?? body.errorMessage, sensitiveValues);
48
+ throw new KnowledgeManifestError(safeCode, `Knowledge manifest request failed with code ${safeCode}${message ? `: ${message}` : ""}`);
49
+ }
3
50
  function unwrapManifestPayload(payload) {
4
51
  if (payload && typeof payload === "object" && "data" in payload) {
5
52
  return payload.data;
@@ -16,7 +63,7 @@ export class ManifestClient {
16
63
  this.fetchImpl = options.fetchImpl ?? fetch;
17
64
  }
18
65
  async fetchManifest(request) {
19
- const response = await this.fetchImpl(`${this.baseUrl}/api/knowledge/manifest`, {
66
+ const response = await this.fetchImpl(`${this.baseUrl}/api/report-cli/knowledge/manifest`, {
20
67
  method: "POST",
21
68
  headers: {
22
69
  "corporation-id": this.profile.corporationId,
@@ -32,8 +79,10 @@ export class ManifestClient {
32
79
  })
33
80
  });
34
81
  if (!response.ok) {
35
- throw new Error(`Knowledge manifest request failed: ${response.status} ${response.statusText}`.trim());
82
+ throw new KnowledgeManifestError("KNOWLEDGE_MANIFEST_HTTP_ERROR", `Knowledge manifest request failed: ${response.status} ${response.statusText}`.trim());
36
83
  }
37
- return parseKnowledgeManifest(unwrapManifestPayload(await response.json()));
84
+ const payload = await response.json();
85
+ throwIfBusinessError(payload, [this.profile.token]);
86
+ return parseKnowledgeManifest(unwrapManifestPayload(payload));
38
87
  }
39
88
  }
@@ -10,6 +10,8 @@ export const knowledgeArtifactTypes = [
10
10
  "reportMenuTree",
11
11
  "reportCompositions",
12
12
  "reportMergedFields",
13
+ "reportMetricsByDim",
14
+ "reportMergedHeads",
13
15
  "reportQueryPlans",
14
16
  "reportFilterSurfaces",
15
17
  "reportEntityBindings",
@@ -82,6 +82,34 @@ const reportMergedFieldRecordSchema = fieldRecordSchema.extend({
82
82
  aliases: z.array(z.string()),
83
83
  tags: z.array(z.string())
84
84
  });
85
+ const reportMetricsByDimRecordSchema = z.object({
86
+ reportId: z.number().int().positive(),
87
+ sourceReportId: z.number().int().positive(),
88
+ modelId: z.number().int().positive(),
89
+ dimensionFieldName: z.string().min(1),
90
+ dimensionDisplayText: z.string().min(1),
91
+ onlyMetricsByDim: z.boolean(),
92
+ disableMetricsByDim: z.boolean(),
93
+ metricFieldNames: z.array(z.string().min(1)),
94
+ defaultMetricFieldNames: z.array(z.string().min(1)),
95
+ rawMetricsByDimConfig: z.string().nullable().optional(),
96
+ source: z.string().min(1)
97
+ });
98
+ const reportMergedHeadRecordSchema = z.object({
99
+ reportId: z.number().int().positive(),
100
+ sourceReportId: z.number().int().positive(),
101
+ modelId: z.number().int().positive(),
102
+ mergedHeadId: z.string().min(1),
103
+ defaultSortIndex: z.number().int(),
104
+ primaryHeadMetricsCode: z.string().min(1).nullable().optional(),
105
+ subHeadIsDynamic: z.boolean(),
106
+ subHeadMetricsCodes: z.array(z.string().min(1)),
107
+ subHeadByDimCode: z.string().min(1).nullable().optional(),
108
+ displaySubTotal: z.boolean(),
109
+ subTotalMetricsCode: z.string().min(1).nullable().optional(),
110
+ rawMergedHeadConfig: z.string().min(1),
111
+ source: z.string().min(1)
112
+ });
85
113
  const reportQueryPlanRecordSchema = z.object({
86
114
  reportId: z.number().int(),
87
115
  planId: z.string().min(1),
@@ -385,6 +413,12 @@ function localizeReportFilterSurfaceRecord(surface, translations) {
385
413
  ...(aliases ? { aliases } : {})
386
414
  };
387
415
  }
416
+ function localizeReportMetricsByDimRecord(record, translations) {
417
+ return {
418
+ ...record,
419
+ dimensionDisplayText: translations.get(record.dimensionFieldName) ?? localizeText(record.dimensionDisplayText, translations)
420
+ };
421
+ }
388
422
  function rankByMatch(query, exactIdentifier, exactText, substringFields, overlapFields) {
389
423
  const trimmedQuery = query.trim();
390
424
  const normalizedQuery = normalizeText(query);
@@ -455,6 +489,8 @@ export class SearchIndex {
455
489
  reportMenuTree;
456
490
  reportCompositions;
457
491
  reportMergedFields;
492
+ reportMetricsByDim;
493
+ reportMergedHeads;
458
494
  reportQueryPlans;
459
495
  reportFilterSurfaces;
460
496
  reportEntityBindings;
@@ -471,6 +507,8 @@ export class SearchIndex {
471
507
  this.reportMenuTree = parseRecords(records.reportMenuTree ?? [], "report menu tree", reportMenuTreeRecordSchema).map((item) => localizeReportMenuTreeRecord(item, translations));
472
508
  this.reportCompositions = parseRecords(records.reportCompositions ?? [], "report compositions", reportCompositionRecordSchema);
473
509
  this.reportMergedFields = parseRecords(records.reportMergedFields ?? [], "report merged fields", reportMergedFieldRecordSchema).map((field) => localizeFieldRecord(field, translations));
510
+ this.reportMetricsByDim = parseRecords(records.reportMetricsByDim ?? [], "report metrics by dim", reportMetricsByDimRecordSchema).map((record) => localizeReportMetricsByDimRecord(record, translations));
511
+ this.reportMergedHeads = parseRecords(records.reportMergedHeads ?? [], "report merged heads", reportMergedHeadRecordSchema);
474
512
  this.reportQueryPlans = parseRecords(records.reportQueryPlans ?? [], "report query plans", reportQueryPlanRecordSchema);
475
513
  this.reportFilterSurfaces = parseRecords(records.reportFilterSurfaces ?? [], "report filter surfaces", reportFilterSurfaceRecordSchema).map((surface) => localizeReportFilterSurfaceRecord(surface, translations));
476
514
  this.reportEntityBindings = parseRecords(records.reportEntityBindings ?? [], "report entity bindings", reportEntityBindingRecordSchema);
@@ -488,6 +526,8 @@ export class SearchIndex {
488
526
  reportMenuTree: parseOptionalJsonlFile(join(root, "report-menu-tree.jsonl"), reportMenuTreeRecordSchema),
489
527
  reportCompositions: parseOptionalJsonlFile(join(root, "report-compositions.jsonl"), reportCompositionRecordSchema),
490
528
  reportMergedFields: parseOptionalJsonlFile(join(root, "report-merged-fields.jsonl"), reportMergedFieldRecordSchema),
529
+ reportMetricsByDim: parseOptionalJsonlFile(join(root, "report-metrics-by-dim.jsonl"), reportMetricsByDimRecordSchema),
530
+ reportMergedHeads: parseOptionalJsonlFile(join(root, "report-merged-heads.jsonl"), reportMergedHeadRecordSchema),
491
531
  reportQueryPlans: parseOptionalJsonlFile(join(root, "report-query-plans.jsonl"), reportQueryPlanRecordSchema),
492
532
  reportFilterSurfaces: parseOptionalJsonlFile(join(root, "report-filter-surfaces.jsonl"), reportFilterSurfaceRecordSchema),
493
533
  reportEntityBindings: parseOptionalJsonlFile(join(root, "report-entity-bindings.jsonl"), reportEntityBindingRecordSchema),
@@ -517,6 +557,8 @@ export class SearchIndex {
517
557
  reportMenuTree: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportMenuTree"), reportMenuTreeRecordSchema),
518
558
  reportCompositions: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportCompositions"), reportCompositionRecordSchema),
519
559
  reportMergedFields: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportMergedFields"), reportMergedFieldRecordSchema),
560
+ reportMetricsByDim: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportMetricsByDim"), reportMetricsByDimRecordSchema),
561
+ reportMergedHeads: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportMergedHeads"), reportMergedHeadRecordSchema),
520
562
  reportQueryPlans: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportQueryPlans"), reportQueryPlanRecordSchema),
521
563
  reportFilterSurfaces: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportFilterSurfaces"), reportFilterSurfaceRecordSchema),
522
564
  reportEntityBindings: parseOptionalJsonlFile(localStore.currentArtifactPath(env, "reportEntityBindings"), reportEntityBindingRecordSchema),
@@ -609,6 +651,8 @@ export class SearchIndex {
609
651
  const routeMap = this.reportRouteMap.filter((route) => route.reportId === report.reportId);
610
652
  const menuTree = this.reportMenuTree.filter((item) => pages.some((page) => page.routeKey === item.key));
611
653
  const composition = this.reportCompositions.find((candidate) => candidate.reportId === report.reportId);
654
+ const metricsByDim = this.reportMetricsByDim.filter((record) => record.reportId === report.reportId);
655
+ const mergedHeads = this.reportMergedHeads.filter((record) => record.reportId === report.reportId);
612
656
  const queryPlans = this.reportQueryPlans.filter((plan) => plan.reportId === report.reportId);
613
657
  const filterSurfaces = this.reportFilterSurfaces.filter((surface) => surface.reportId === report.reportId);
614
658
  const entityBindings = this.reportEntityBindings.filter((binding) => binding.reportId === report.reportId);
@@ -619,6 +663,8 @@ export class SearchIndex {
619
663
  ...(routeMap.length > 0 ? { routeMap } : {}),
620
664
  ...(menuTree.length > 0 ? { menuTree } : {}),
621
665
  ...(composition ? { composition } : {}),
666
+ ...(metricsByDim.length > 0 ? { metricsByDim } : {}),
667
+ ...(mergedHeads.length > 0 ? { mergedHeads } : {}),
622
668
  ...(queryPlans.length > 0 ? { queryPlans } : {}),
623
669
  ...(filterSurfaces.length > 0 ? { filterSurfaces } : {}),
624
670
  ...(entityBindings.length > 0 ? { entityBindings } : {})