@tiangong-lca/cli 0.0.2 → 0.0.4
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.
- package/README.md +131 -309
- package/dist/src/cli.js +158 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-command.js +122 -0
- package/dist/src/lib/dataset-command.js.map +1 -0
- package/dist/src/lib/flow-fetch-rows.js +235 -0
- package/dist/src/lib/flow-fetch-rows.js.map +1 -0
- package/dist/src/lib/flow-materialize-decisions.js +421 -0
- package/dist/src/lib/flow-materialize-decisions.js.map +1 -0
- package/dist/src/lib/flow-publish-version.js +23 -19
- package/dist/src/lib/flow-publish-version.js.map +1 -1
- package/dist/src/lib/remote.js +2 -2
- package/dist/src/lib/remote.js.map +1 -1
- package/dist/src/lib/supabase-client.js +5 -2
- package/dist/src/lib/supabase-client.js.map +1 -1
- package/dist/src/lib/supabase-json-ordered-write.js +42 -21
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { CliError } from './errors.js';
|
|
2
|
+
import { createSupabaseFetch, deriveSupabaseFunctionsBaseUrl, } from './supabase-client.js';
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function trimToken(value) {
|
|
7
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
}
|
|
9
|
+
function command_endpoint(command) {
|
|
10
|
+
return command === 'create' ? 'app_dataset_create' : 'app_dataset_save_draft';
|
|
11
|
+
}
|
|
12
|
+
export function buildDatasetCommandUrl(apiBaseUrl, command) {
|
|
13
|
+
return `${deriveSupabaseFunctionsBaseUrl(apiBaseUrl)}/${command_endpoint(command)}`;
|
|
14
|
+
}
|
|
15
|
+
export function buildDatasetCommandHeaders(region) {
|
|
16
|
+
const headers = {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
const normalizedRegion = trimToken(region);
|
|
20
|
+
if (normalizedRegion) {
|
|
21
|
+
headers['x-region'] = normalizedRegion;
|
|
22
|
+
}
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
export function buildDatasetCommandBody(command, input) {
|
|
26
|
+
const body = {
|
|
27
|
+
table: input.table,
|
|
28
|
+
id: input.id,
|
|
29
|
+
jsonOrdered: input.jsonOrdered,
|
|
30
|
+
};
|
|
31
|
+
if (command === 'save_draft') {
|
|
32
|
+
body.version = input.version;
|
|
33
|
+
}
|
|
34
|
+
if ('modelId' in input && input.modelId !== undefined) {
|
|
35
|
+
body.modelId = input.modelId;
|
|
36
|
+
}
|
|
37
|
+
if ('ruleVerification' in input && input.ruleVerification !== undefined) {
|
|
38
|
+
body.ruleVerification = input.ruleVerification;
|
|
39
|
+
}
|
|
40
|
+
return body;
|
|
41
|
+
}
|
|
42
|
+
function parseJsonText(rawText, url) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(rawText);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new CliError(`Remote response was not valid JSON for ${url}`, {
|
|
48
|
+
code: 'REMOTE_INVALID_JSON',
|
|
49
|
+
exitCode: 1,
|
|
50
|
+
details: String(error),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isDatasetCommandFailurePayload(value) {
|
|
55
|
+
return (isRecord(value) &&
|
|
56
|
+
value.ok === false &&
|
|
57
|
+
typeof value.code === 'string' &&
|
|
58
|
+
typeof value.message === 'string');
|
|
59
|
+
}
|
|
60
|
+
function unwrapDatasetCommandPayload(payload) {
|
|
61
|
+
if (isDatasetCommandFailurePayload(payload)) {
|
|
62
|
+
throw new CliError(payload.message, {
|
|
63
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
details: `${payload.code}: ${payload.message}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (isRecord(payload) && payload.ok === true && 'data' in payload) {
|
|
69
|
+
return payload.data ?? null;
|
|
70
|
+
}
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
73
|
+
function parseDatasetCommandResponse(response, url, rawText) {
|
|
74
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
75
|
+
const parsed = rawText.length === 0
|
|
76
|
+
? null
|
|
77
|
+
: contentType.includes('application/json')
|
|
78
|
+
? parseJsonText(rawText, url)
|
|
79
|
+
: rawText;
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
if (isDatasetCommandFailurePayload(parsed)) {
|
|
82
|
+
throw new CliError(`HTTP ${response.status} returned from ${url}`, {
|
|
83
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
84
|
+
exitCode: 1,
|
|
85
|
+
details: `${parsed.code}: ${parsed.message}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
throw new CliError(`HTTP ${response.status} returned from ${url}`, {
|
|
89
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
90
|
+
exitCode: 1,
|
|
91
|
+
details: typeof parsed === 'string' ? parsed : rawText || undefined,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return unwrapDatasetCommandPayload(parsed);
|
|
95
|
+
}
|
|
96
|
+
async function executeDatasetCommand(fetchWithAuth, url, headers, body) {
|
|
97
|
+
const response = await fetchWithAuth(url, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers,
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
});
|
|
102
|
+
return parseDatasetCommandResponse(response, url, await response.text());
|
|
103
|
+
}
|
|
104
|
+
export function createDatasetCommandClient(options) {
|
|
105
|
+
const fetchWithAuth = createSupabaseFetch(options.fetchImpl, options.timeoutMs, options.runtime);
|
|
106
|
+
const headers = buildDatasetCommandHeaders(options.region);
|
|
107
|
+
const createUrl = buildDatasetCommandUrl(options.runtime.apiBaseUrl, 'create');
|
|
108
|
+
const saveDraftUrl = buildDatasetCommandUrl(options.runtime.apiBaseUrl, 'save_draft');
|
|
109
|
+
return {
|
|
110
|
+
create: (input) => executeDatasetCommand(fetchWithAuth, createUrl, headers, buildDatasetCommandBody('create', input)),
|
|
111
|
+
saveDraft: (input) => executeDatasetCommand(fetchWithAuth, saveDraftUrl, headers, buildDatasetCommandBody('save_draft', input)),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export const __testInternals = {
|
|
115
|
+
buildDatasetCommandBody,
|
|
116
|
+
buildDatasetCommandHeaders,
|
|
117
|
+
buildDatasetCommandUrl,
|
|
118
|
+
command_endpoint,
|
|
119
|
+
parseDatasetCommandResponse,
|
|
120
|
+
unwrapDatasetCommandPayload,
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=dataset-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-command.js","sourceRoot":"","sources":["../../../src/lib/dataset-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors.js';\nimport type { ResponseLike } from './http.js';\nimport {\n createSupabaseFetch,\n deriveSupabaseFunctionsBaseUrl,\n type SupabaseDataRuntime,\n} from './supabase-client.js';\n\ntype JsonObject = Record<string, unknown>;\n\nexport type DatasetCommandTable =\n | 'contacts'\n | 'sources'\n | 'unitgroups'\n | 'flowproperties'\n | 'flows'\n | 'processes'\n | 'lifecyclemodels';\n\nexport type DatasetCommandName = 'create' | 'save_draft';\n\ntype DatasetCommandFailurePayload = {\n ok: false;\n code: string;\n message: string;\n details?: unknown;\n};\n\ntype DatasetCommandSuccessEnvelope = {\n ok: true;\n data?: unknown;\n};\n\nexport type DatasetCommandCreateInput = {\n table: DatasetCommandTable;\n id: string;\n jsonOrdered: unknown;\n modelId?: string | null;\n ruleVerification?: boolean | null;\n};\n\nexport type DatasetCommandSaveDraftInput = DatasetCommandCreateInput & {\n version: string;\n};\n\nexport type DatasetCommandClient = {\n create: (input: DatasetCommandCreateInput) => Promise<unknown>;\n saveDraft: (input: DatasetCommandSaveDraftInput) => Promise<unknown>;\n};\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction trimToken(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction command_endpoint(command: DatasetCommandName): string {\n return command === 'create' ? 'app_dataset_create' : 'app_dataset_save_draft';\n}\n\nexport function buildDatasetCommandUrl(apiBaseUrl: string, command: DatasetCommandName): string {\n return `${deriveSupabaseFunctionsBaseUrl(apiBaseUrl)}/${command_endpoint(command)}`;\n}\n\nexport function buildDatasetCommandHeaders(\n region: string | null | undefined,\n): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n const normalizedRegion = trimToken(region);\n if (normalizedRegion) {\n headers['x-region'] = normalizedRegion;\n }\n return headers;\n}\n\nexport function buildDatasetCommandBody(\n command: DatasetCommandName,\n input: DatasetCommandCreateInput | DatasetCommandSaveDraftInput,\n): JsonObject {\n const body: JsonObject = {\n table: input.table,\n id: input.id,\n jsonOrdered: input.jsonOrdered,\n };\n\n if (command === 'save_draft') {\n body.version = (input as DatasetCommandSaveDraftInput).version;\n }\n\n if ('modelId' in input && input.modelId !== undefined) {\n body.modelId = input.modelId;\n }\n\n if ('ruleVerification' in input && input.ruleVerification !== undefined) {\n body.ruleVerification = input.ruleVerification;\n }\n\n return body;\n}\n\nfunction parseJsonText(rawText: string, url: string): unknown {\n try {\n return JSON.parse(rawText);\n } catch (error) {\n throw new CliError(`Remote response was not valid JSON for ${url}`, {\n code: 'REMOTE_INVALID_JSON',\n exitCode: 1,\n details: String(error),\n });\n }\n}\n\nfunction isDatasetCommandFailurePayload(value: unknown): value is DatasetCommandFailurePayload {\n return (\n isRecord(value) &&\n value.ok === false &&\n typeof value.code === 'string' &&\n typeof value.message === 'string'\n );\n}\n\nfunction unwrapDatasetCommandPayload(payload: unknown): unknown {\n if (isDatasetCommandFailurePayload(payload)) {\n throw new CliError(payload.message, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: `${payload.code}: ${payload.message}`,\n });\n }\n\n if (isRecord(payload) && payload.ok === true && 'data' in payload) {\n return (payload as DatasetCommandSuccessEnvelope).data ?? null;\n }\n\n return payload;\n}\n\nfunction parseDatasetCommandResponse(\n response: ResponseLike,\n url: string,\n rawText: string,\n): unknown {\n const contentType = response.headers.get('content-type') ?? '';\n const parsed =\n rawText.length === 0\n ? null\n : contentType.includes('application/json')\n ? parseJsonText(rawText, url)\n : rawText;\n\n if (!response.ok) {\n if (isDatasetCommandFailurePayload(parsed)) {\n throw new CliError(`HTTP ${response.status} returned from ${url}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: `${parsed.code}: ${parsed.message}`,\n });\n }\n\n throw new CliError(`HTTP ${response.status} returned from ${url}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: typeof parsed === 'string' ? parsed : rawText || undefined,\n });\n }\n\n return unwrapDatasetCommandPayload(parsed);\n}\n\nasync function executeDatasetCommand(\n fetchWithAuth: typeof fetch,\n url: string,\n headers: Record<string, string>,\n body: JsonObject,\n): Promise<unknown> {\n const response = await fetchWithAuth(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n return parseDatasetCommandResponse(response, url, await response.text());\n}\n\nexport function createDatasetCommandClient(options: {\n runtime: SupabaseDataRuntime;\n fetchImpl: (input: string, init?: RequestInit) => Promise<ResponseLike>;\n timeoutMs: number;\n region?: string | null;\n}): DatasetCommandClient {\n const fetchWithAuth = createSupabaseFetch(options.fetchImpl, options.timeoutMs, options.runtime);\n const headers = buildDatasetCommandHeaders(options.region);\n const createUrl = buildDatasetCommandUrl(options.runtime.apiBaseUrl, 'create');\n const saveDraftUrl = buildDatasetCommandUrl(options.runtime.apiBaseUrl, 'save_draft');\n\n return {\n create: (input) =>\n executeDatasetCommand(\n fetchWithAuth,\n createUrl,\n headers,\n buildDatasetCommandBody('create', input),\n ),\n saveDraft: (input) =>\n executeDatasetCommand(\n fetchWithAuth,\n saveDraftUrl,\n headers,\n buildDatasetCommandBody('save_draft', input),\n ),\n };\n}\n\nexport const __testInternals = {\n buildDatasetCommandBody,\n buildDatasetCommandHeaders,\n buildDatasetCommandUrl,\n command_endpoint,\n parseDatasetCommandResponse,\n unwrapDatasetCommandPayload,\n};\n"]}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
|
|
3
|
+
import { CliError } from './errors.js';
|
|
4
|
+
import { loadRowsFromFile } from './flow-governance.js';
|
|
5
|
+
import { fetchOneFlowRow, normalizeSupabaseFlowPayload, } from './flow-read.js';
|
|
6
|
+
import { requireSupabaseRestRuntime } from './supabase-rest.js';
|
|
7
|
+
import { createSupabaseDataRuntime } from './supabase-session.js';
|
|
8
|
+
const FLOW_FETCH_ROWS_TIMEOUT_MS = 10_000;
|
|
9
|
+
function normalizeToken(value) {
|
|
10
|
+
if (value === undefined || value === null) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const trimmed = String(value).trim();
|
|
14
|
+
return trimmed ? trimmed : null;
|
|
15
|
+
}
|
|
16
|
+
function normalizeOptionalNonNegativeInteger(value, label, code) {
|
|
17
|
+
if (value === undefined || value === null || value === '') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const parsed = typeof value === 'number' && Number.isInteger(value)
|
|
21
|
+
? value
|
|
22
|
+
: Number.parseInt(String(value), 10);
|
|
23
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
24
|
+
throw new CliError(`Expected ${label} to be a non-negative integer.`, {
|
|
25
|
+
code,
|
|
26
|
+
exitCode: 2,
|
|
27
|
+
details: value,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
function normalizeFlowFetchRef(row, index) {
|
|
33
|
+
const id = normalizeToken(row.id);
|
|
34
|
+
if (!id) {
|
|
35
|
+
throw new CliError(`Flow ref row ${index + 1} is missing required id.`, {
|
|
36
|
+
code: 'FLOW_FETCH_ROWS_REF_ID_REQUIRED',
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
details: row,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
version: normalizeToken(row.version),
|
|
44
|
+
userId: normalizeToken(row.user_id ?? row.userId),
|
|
45
|
+
stateCode: normalizeOptionalNonNegativeInteger(row.state_code ?? row.stateCode, 'flow ref state_code', 'FLOW_FETCH_ROWS_INVALID_STATE_CODE'),
|
|
46
|
+
clusterId: normalizeToken(row.cluster_id ?? row.clusterId),
|
|
47
|
+
source: normalizeToken(row.source),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function toRequestedRefSummary(ref) {
|
|
51
|
+
return {
|
|
52
|
+
id: ref.id,
|
|
53
|
+
version: ref.version,
|
|
54
|
+
user_id: ref.userId,
|
|
55
|
+
state_code: ref.stateCode,
|
|
56
|
+
cluster_id: ref.clusterId,
|
|
57
|
+
source: ref.source,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function buildMaterializedRow(lookup, context) {
|
|
61
|
+
const resolvedFlowId = lookup.row.id || context.requested_ref.id;
|
|
62
|
+
const resolvedVersion = lookup.row.version || context.requested_ref.version || '';
|
|
63
|
+
return {
|
|
64
|
+
id: resolvedFlowId,
|
|
65
|
+
version: resolvedVersion,
|
|
66
|
+
user_id: lookup.row.user_id,
|
|
67
|
+
state_code: lookup.row.state_code,
|
|
68
|
+
modified_at: lookup.row.modified_at,
|
|
69
|
+
json: normalizeSupabaseFlowPayload(lookup.row.json, `${resolvedFlowId}@${resolvedVersion}`),
|
|
70
|
+
_materialization: context,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function buildReviewInputRow(row, flowKey, contexts) {
|
|
74
|
+
return {
|
|
75
|
+
...row,
|
|
76
|
+
_materialization: {
|
|
77
|
+
flow_key: flowKey,
|
|
78
|
+
materialized_ref_count: contexts.length,
|
|
79
|
+
materialized_from_refs: contexts,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function nowIso(now = new Date()) {
|
|
84
|
+
return now.toISOString();
|
|
85
|
+
}
|
|
86
|
+
export async function runFlowFetchRows(options) {
|
|
87
|
+
const refsFile = normalizeToken(options.refsFile);
|
|
88
|
+
if (!refsFile) {
|
|
89
|
+
throw new CliError('Missing required --refs-file value.', {
|
|
90
|
+
code: 'FLOW_FETCH_ROWS_REFS_FILE_REQUIRED',
|
|
91
|
+
exitCode: 2,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const outDir = normalizeToken(options.outDir);
|
|
95
|
+
if (!outDir) {
|
|
96
|
+
throw new CliError('Missing required --out-dir value.', {
|
|
97
|
+
code: 'FLOW_FETCH_ROWS_OUT_DIR_REQUIRED',
|
|
98
|
+
exitCode: 2,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const resolvedRefsFile = path.resolve(refsFile);
|
|
102
|
+
const resolvedOutDir = path.resolve(outDir);
|
|
103
|
+
const allowLatestFallback = options.allowLatestFallback !== false;
|
|
104
|
+
const rows = loadRowsFromFile(resolvedRefsFile);
|
|
105
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
106
|
+
const timeoutMs = options.timeoutMs ?? FLOW_FETCH_ROWS_TIMEOUT_MS;
|
|
107
|
+
const runtime = createSupabaseDataRuntime({
|
|
108
|
+
runtime: requireSupabaseRestRuntime(options.env ?? process.env),
|
|
109
|
+
fetchImpl,
|
|
110
|
+
timeoutMs,
|
|
111
|
+
now: options.now,
|
|
112
|
+
});
|
|
113
|
+
const resolvedRowArtifacts = [];
|
|
114
|
+
const missingRefs = [];
|
|
115
|
+
const ambiguousRefs = [];
|
|
116
|
+
const reviewInputByKey = new Map();
|
|
117
|
+
const resolutionCounts = {
|
|
118
|
+
remote_supabase_exact: 0,
|
|
119
|
+
remote_supabase_latest: 0,
|
|
120
|
+
remote_supabase_latest_fallback: 0,
|
|
121
|
+
};
|
|
122
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
123
|
+
const ref = normalizeFlowFetchRef(rows[index], index);
|
|
124
|
+
const requestedRef = toRequestedRefSummary(ref);
|
|
125
|
+
let lookup;
|
|
126
|
+
try {
|
|
127
|
+
lookup = await fetchOneFlowRow({
|
|
128
|
+
runtime,
|
|
129
|
+
id: ref.id,
|
|
130
|
+
version: ref.version,
|
|
131
|
+
userId: ref.userId,
|
|
132
|
+
stateCode: ref.stateCode,
|
|
133
|
+
timeoutMs,
|
|
134
|
+
fetchImpl,
|
|
135
|
+
fallbackToLatest: allowLatestFallback && ref.version !== null,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (error instanceof CliError && error.code === 'FLOW_GET_AMBIGUOUS') {
|
|
140
|
+
ambiguousRefs.push({
|
|
141
|
+
input_index: index,
|
|
142
|
+
requested_ref: requestedRef,
|
|
143
|
+
code: error.code,
|
|
144
|
+
message: error.message,
|
|
145
|
+
details: error.details,
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
if (!lookup) {
|
|
152
|
+
missingRefs.push({
|
|
153
|
+
input_index: index,
|
|
154
|
+
requested_ref: requestedRef,
|
|
155
|
+
code: 'FLOW_GET_NOT_FOUND',
|
|
156
|
+
message: ref.version
|
|
157
|
+
? `Could not resolve flow dataset for ${ref.id}@${ref.version}.`
|
|
158
|
+
: `Could not resolve flow dataset for ${ref.id}.`,
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
resolutionCounts[lookup.resolution] += 1;
|
|
163
|
+
const resolvedFlowId = lookup.row.id || ref.id;
|
|
164
|
+
const resolvedVersion = lookup.row.version || ref.version || '';
|
|
165
|
+
const context = {
|
|
166
|
+
input_index: index,
|
|
167
|
+
requested_ref: requestedRef,
|
|
168
|
+
resolution: lookup.resolution,
|
|
169
|
+
source_url: lookup.sourceUrl,
|
|
170
|
+
resolved_flow_id: resolvedFlowId,
|
|
171
|
+
resolved_version: resolvedVersion,
|
|
172
|
+
};
|
|
173
|
+
const materializedRow = buildMaterializedRow(lookup, context);
|
|
174
|
+
resolvedRowArtifacts.push(materializedRow);
|
|
175
|
+
const flowKey = `${resolvedFlowId}@${resolvedVersion}`;
|
|
176
|
+
const existing = reviewInputByKey.get(flowKey);
|
|
177
|
+
if (existing) {
|
|
178
|
+
existing.contexts.push(context);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
reviewInputByKey.set(flowKey, {
|
|
182
|
+
row: materializedRow,
|
|
183
|
+
contexts: [context],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const reviewInputRows = [...reviewInputByKey.entries()]
|
|
188
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
189
|
+
.map(([flowKey, entry]) => buildReviewInputRow(entry.row, flowKey, entry.contexts));
|
|
190
|
+
const duplicateReviewInputRowsCollapsed = resolvedRowArtifacts.length - reviewInputRows.length;
|
|
191
|
+
const unresolvedRefCount = missingRefs.length + ambiguousRefs.length;
|
|
192
|
+
const status = unresolvedRefCount > 0
|
|
193
|
+
? 'completed_flow_row_materialization_with_gaps'
|
|
194
|
+
: 'completed_flow_row_materialization';
|
|
195
|
+
const resolvedRowsPath = path.join(resolvedOutDir, 'resolved-flow-rows.jsonl');
|
|
196
|
+
const reviewInputRowsPath = path.join(resolvedOutDir, 'review-input-rows.jsonl');
|
|
197
|
+
const missingRefsPath = path.join(resolvedOutDir, 'missing-flow-refs.jsonl');
|
|
198
|
+
const ambiguousRefsPath = path.join(resolvedOutDir, 'ambiguous-flow-refs.jsonl');
|
|
199
|
+
const summaryPath = path.join(resolvedOutDir, 'fetch-summary.json');
|
|
200
|
+
writeJsonLinesArtifact(resolvedRowsPath, resolvedRowArtifacts);
|
|
201
|
+
writeJsonLinesArtifact(reviewInputRowsPath, reviewInputRows);
|
|
202
|
+
writeJsonLinesArtifact(missingRefsPath, missingRefs);
|
|
203
|
+
writeJsonLinesArtifact(ambiguousRefsPath, ambiguousRefs);
|
|
204
|
+
const report = {
|
|
205
|
+
schema_version: 1,
|
|
206
|
+
generated_at_utc: nowIso(options.now),
|
|
207
|
+
status,
|
|
208
|
+
refs_file: resolvedRefsFile,
|
|
209
|
+
out_dir: resolvedOutDir,
|
|
210
|
+
allow_latest_fallback: allowLatestFallback,
|
|
211
|
+
requested_ref_count: rows.length,
|
|
212
|
+
resolved_ref_count: resolvedRowArtifacts.length,
|
|
213
|
+
review_input_row_count: reviewInputRows.length,
|
|
214
|
+
duplicate_review_input_rows_collapsed: duplicateReviewInputRowsCollapsed,
|
|
215
|
+
missing_ref_count: missingRefs.length,
|
|
216
|
+
ambiguous_ref_count: ambiguousRefs.length,
|
|
217
|
+
resolution_counts: resolutionCounts,
|
|
218
|
+
files: {
|
|
219
|
+
resolved_flow_rows: resolvedRowsPath,
|
|
220
|
+
review_input_rows: reviewInputRowsPath,
|
|
221
|
+
fetch_summary: summaryPath,
|
|
222
|
+
missing_flow_refs: missingRefsPath,
|
|
223
|
+
ambiguous_flow_refs: ambiguousRefsPath,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
writeJsonArtifact(summaryPath, report);
|
|
227
|
+
return report;
|
|
228
|
+
}
|
|
229
|
+
export const __testInternals = {
|
|
230
|
+
normalizeFlowFetchRef,
|
|
231
|
+
normalizeOptionalNonNegativeInteger,
|
|
232
|
+
normalizeToken,
|
|
233
|
+
toRequestedRefSummary,
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=flow-fetch-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-fetch-rows.js","sourceRoot":"","sources":["../../../src/lib/flow-fetch-rows.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';\nimport { CliError } from './errors.js';\nimport { loadRowsFromFile, type JsonRecord } from './flow-governance.js';\nimport type { FetchLike } from './http.js';\nimport {\n fetchOneFlowRow,\n normalizeSupabaseFlowPayload,\n type SupabaseFlowLookup,\n} from './flow-read.js';\nimport { requireSupabaseRestRuntime } from './supabase-rest.js';\nimport { createSupabaseDataRuntime } from './supabase-session.js';\n\nconst FLOW_FETCH_ROWS_TIMEOUT_MS = 10_000;\n\ntype FlowFetchRef = {\n id: string;\n version: string | null;\n userId: string | null;\n stateCode: number | null;\n clusterId: string | null;\n source: string | null;\n};\n\ntype FlowFetchMaterializationContext = {\n input_index: number;\n requested_ref: {\n id: string;\n version: string | null;\n user_id: string | null;\n state_code: number | null;\n cluster_id: string | null;\n source: string | null;\n };\n resolution: SupabaseFlowLookup['resolution'];\n source_url: string;\n resolved_flow_id: string;\n resolved_version: string;\n};\n\ntype FlowFetchSummaryStatus =\n | 'completed_flow_row_materialization'\n | 'completed_flow_row_materialization_with_gaps';\n\nexport type RunFlowFetchRowsOptions = {\n refsFile: string;\n outDir: string;\n allowLatestFallback?: boolean;\n env?: NodeJS.ProcessEnv;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n now?: Date;\n};\n\nexport type FlowFetchRowsReport = {\n schema_version: 1;\n generated_at_utc: string;\n status: FlowFetchSummaryStatus;\n refs_file: string;\n out_dir: string;\n allow_latest_fallback: boolean;\n requested_ref_count: number;\n resolved_ref_count: number;\n review_input_row_count: number;\n duplicate_review_input_rows_collapsed: number;\n missing_ref_count: number;\n ambiguous_ref_count: number;\n resolution_counts: Record<SupabaseFlowLookup['resolution'], number>;\n files: {\n resolved_flow_rows: string;\n review_input_rows: string;\n fetch_summary: string;\n missing_flow_refs: string;\n ambiguous_flow_refs: string;\n };\n};\n\nfunction normalizeToken(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n\n const trimmed = String(value).trim();\n return trimmed ? trimmed : null;\n}\n\nfunction normalizeOptionalNonNegativeInteger(\n value: unknown,\n label: string,\n code: string,\n): number | null {\n if (value === undefined || value === null || value === '') {\n return null;\n }\n\n const parsed =\n typeof value === 'number' && Number.isInteger(value)\n ? value\n : Number.parseInt(String(value), 10);\n if (!Number.isInteger(parsed) || parsed < 0) {\n throw new CliError(`Expected ${label} to be a non-negative integer.`, {\n code,\n exitCode: 2,\n details: value,\n });\n }\n return parsed;\n}\n\nfunction normalizeFlowFetchRef(row: JsonRecord, index: number): FlowFetchRef {\n const id = normalizeToken(row.id);\n if (!id) {\n throw new CliError(`Flow ref row ${index + 1} is missing required id.`, {\n code: 'FLOW_FETCH_ROWS_REF_ID_REQUIRED',\n exitCode: 2,\n details: row,\n });\n }\n\n return {\n id,\n version: normalizeToken(row.version),\n userId: normalizeToken(row.user_id ?? row.userId),\n stateCode: normalizeOptionalNonNegativeInteger(\n row.state_code ?? row.stateCode,\n 'flow ref state_code',\n 'FLOW_FETCH_ROWS_INVALID_STATE_CODE',\n ),\n clusterId: normalizeToken(row.cluster_id ?? row.clusterId),\n source: normalizeToken(row.source),\n };\n}\n\nfunction toRequestedRefSummary(\n ref: FlowFetchRef,\n): FlowFetchMaterializationContext['requested_ref'] {\n return {\n id: ref.id,\n version: ref.version,\n user_id: ref.userId,\n state_code: ref.stateCode,\n cluster_id: ref.clusterId,\n source: ref.source,\n };\n}\n\nfunction buildMaterializedRow(\n lookup: SupabaseFlowLookup,\n context: FlowFetchMaterializationContext,\n): JsonRecord {\n const resolvedFlowId = lookup.row.id || context.requested_ref.id;\n const resolvedVersion = lookup.row.version || context.requested_ref.version || '';\n\n return {\n id: resolvedFlowId,\n version: resolvedVersion,\n user_id: lookup.row.user_id,\n state_code: lookup.row.state_code,\n modified_at: lookup.row.modified_at,\n json: normalizeSupabaseFlowPayload(lookup.row.json, `${resolvedFlowId}@${resolvedVersion}`),\n _materialization: context,\n };\n}\n\nfunction buildReviewInputRow(\n row: JsonRecord,\n flowKey: string,\n contexts: FlowFetchMaterializationContext[],\n): JsonRecord {\n return {\n ...row,\n _materialization: {\n flow_key: flowKey,\n materialized_ref_count: contexts.length,\n materialized_from_refs: contexts,\n },\n };\n}\n\nfunction nowIso(now: Date = new Date()): string {\n return now.toISOString();\n}\n\nexport async function runFlowFetchRows(\n options: RunFlowFetchRowsOptions,\n): Promise<FlowFetchRowsReport> {\n const refsFile = normalizeToken(options.refsFile);\n if (!refsFile) {\n throw new CliError('Missing required --refs-file value.', {\n code: 'FLOW_FETCH_ROWS_REFS_FILE_REQUIRED',\n exitCode: 2,\n });\n }\n\n const outDir = normalizeToken(options.outDir);\n if (!outDir) {\n throw new CliError('Missing required --out-dir value.', {\n code: 'FLOW_FETCH_ROWS_OUT_DIR_REQUIRED',\n exitCode: 2,\n });\n }\n\n const resolvedRefsFile = path.resolve(refsFile);\n const resolvedOutDir = path.resolve(outDir);\n const allowLatestFallback = options.allowLatestFallback !== false;\n const rows = loadRowsFromFile(resolvedRefsFile);\n\n const fetchImpl = options.fetchImpl ?? (fetch as FetchLike);\n const timeoutMs = options.timeoutMs ?? FLOW_FETCH_ROWS_TIMEOUT_MS;\n const runtime = createSupabaseDataRuntime({\n runtime: requireSupabaseRestRuntime(options.env ?? process.env),\n fetchImpl,\n timeoutMs,\n now: options.now,\n });\n\n const resolvedRowArtifacts: JsonRecord[] = [];\n const missingRefs: JsonRecord[] = [];\n const ambiguousRefs: JsonRecord[] = [];\n const reviewInputByKey = new Map<\n string,\n { row: JsonRecord; contexts: FlowFetchMaterializationContext[] }\n >();\n const resolutionCounts: FlowFetchRowsReport['resolution_counts'] = {\n remote_supabase_exact: 0,\n remote_supabase_latest: 0,\n remote_supabase_latest_fallback: 0,\n };\n\n for (let index = 0; index < rows.length; index += 1) {\n const ref = normalizeFlowFetchRef(rows[index] as JsonRecord, index);\n const requestedRef = toRequestedRefSummary(ref);\n\n let lookup: SupabaseFlowLookup | null;\n try {\n lookup = await fetchOneFlowRow({\n runtime,\n id: ref.id,\n version: ref.version,\n userId: ref.userId,\n stateCode: ref.stateCode,\n timeoutMs,\n fetchImpl,\n fallbackToLatest: allowLatestFallback && ref.version !== null,\n });\n } catch (error) {\n if (error instanceof CliError && error.code === 'FLOW_GET_AMBIGUOUS') {\n ambiguousRefs.push({\n input_index: index,\n requested_ref: requestedRef,\n code: error.code,\n message: error.message,\n details: error.details,\n });\n continue;\n }\n throw error;\n }\n\n if (!lookup) {\n missingRefs.push({\n input_index: index,\n requested_ref: requestedRef,\n code: 'FLOW_GET_NOT_FOUND',\n message: ref.version\n ? `Could not resolve flow dataset for ${ref.id}@${ref.version}.`\n : `Could not resolve flow dataset for ${ref.id}.`,\n });\n continue;\n }\n\n resolutionCounts[lookup.resolution] += 1;\n const resolvedFlowId = lookup.row.id || ref.id;\n const resolvedVersion = lookup.row.version || ref.version || '';\n const context: FlowFetchMaterializationContext = {\n input_index: index,\n requested_ref: requestedRef,\n resolution: lookup.resolution,\n source_url: lookup.sourceUrl,\n resolved_flow_id: resolvedFlowId,\n resolved_version: resolvedVersion,\n };\n const materializedRow = buildMaterializedRow(lookup, context);\n resolvedRowArtifacts.push(materializedRow);\n\n const flowKey = `${resolvedFlowId}@${resolvedVersion}`;\n const existing = reviewInputByKey.get(flowKey);\n if (existing) {\n existing.contexts.push(context);\n } else {\n reviewInputByKey.set(flowKey, {\n row: materializedRow,\n contexts: [context],\n });\n }\n }\n\n const reviewInputRows = [...reviewInputByKey.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([flowKey, entry]) => buildReviewInputRow(entry.row, flowKey, entry.contexts));\n\n const duplicateReviewInputRowsCollapsed = resolvedRowArtifacts.length - reviewInputRows.length;\n const unresolvedRefCount = missingRefs.length + ambiguousRefs.length;\n const status: FlowFetchSummaryStatus =\n unresolvedRefCount > 0\n ? 'completed_flow_row_materialization_with_gaps'\n : 'completed_flow_row_materialization';\n\n const resolvedRowsPath = path.join(resolvedOutDir, 'resolved-flow-rows.jsonl');\n const reviewInputRowsPath = path.join(resolvedOutDir, 'review-input-rows.jsonl');\n const missingRefsPath = path.join(resolvedOutDir, 'missing-flow-refs.jsonl');\n const ambiguousRefsPath = path.join(resolvedOutDir, 'ambiguous-flow-refs.jsonl');\n const summaryPath = path.join(resolvedOutDir, 'fetch-summary.json');\n\n writeJsonLinesArtifact(resolvedRowsPath, resolvedRowArtifacts);\n writeJsonLinesArtifact(reviewInputRowsPath, reviewInputRows);\n writeJsonLinesArtifact(missingRefsPath, missingRefs);\n writeJsonLinesArtifact(ambiguousRefsPath, ambiguousRefs);\n\n const report: FlowFetchRowsReport = {\n schema_version: 1,\n generated_at_utc: nowIso(options.now),\n status,\n refs_file: resolvedRefsFile,\n out_dir: resolvedOutDir,\n allow_latest_fallback: allowLatestFallback,\n requested_ref_count: rows.length,\n resolved_ref_count: resolvedRowArtifacts.length,\n review_input_row_count: reviewInputRows.length,\n duplicate_review_input_rows_collapsed: duplicateReviewInputRowsCollapsed,\n missing_ref_count: missingRefs.length,\n ambiguous_ref_count: ambiguousRefs.length,\n resolution_counts: resolutionCounts,\n files: {\n resolved_flow_rows: resolvedRowsPath,\n review_input_rows: reviewInputRowsPath,\n fetch_summary: summaryPath,\n missing_flow_refs: missingRefsPath,\n ambiguous_flow_refs: ambiguousRefsPath,\n },\n };\n\n writeJsonArtifact(summaryPath, report);\n return report;\n}\n\nexport const __testInternals = {\n normalizeFlowFetchRef,\n normalizeOptionalNonNegativeInteger,\n normalizeToken,\n toRequestedRefSummary,\n};\n"]}
|