@tiangong-lca/cli 0.0.22 → 0.0.24

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.
@@ -0,0 +1,265 @@
1
+ import { CliError } from './errors.js';
2
+ import { DERIVATIVE_REBUILD_COMPONENTS, isJsonObject, } from './dataset-maintenance-contract.js';
3
+ const SHA256 = /^[a-f0-9]{64}$/u;
4
+ const POSITIVE_INTEGER_TEXT = /^[1-9]\d*$/u;
5
+ function requiredToken(value, label) {
6
+ if (typeof value !== 'string' || !value.trim()) {
7
+ throw new CliError(`${label} must be a non-empty string.`, {
8
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
9
+ exitCode: 1,
10
+ });
11
+ }
12
+ return value.trim();
13
+ }
14
+ function requiredSha256(value, label) {
15
+ const normalized = requiredToken(value, label);
16
+ if (!SHA256.test(normalized)) {
17
+ throw new CliError(`${label} must be a lowercase SHA-256 digest.`, {
18
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
19
+ exitCode: 1,
20
+ });
21
+ }
22
+ return normalized;
23
+ }
24
+ function nullableSha256(value, label) {
25
+ return value === null ? null : requiredSha256(value, label);
26
+ }
27
+ function nullableTimestamp(value, label) {
28
+ if (value === null)
29
+ return null;
30
+ const normalized = requiredToken(value, label);
31
+ if (!Number.isFinite(Date.parse(normalized))) {
32
+ throw new CliError(`${label} must be an ISO timestamp or null.`, {
33
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
34
+ exitCode: 1,
35
+ });
36
+ }
37
+ return normalized;
38
+ }
39
+ function requestStatus(value, label) {
40
+ const normalized = requiredToken(value, label);
41
+ const statuses = [
42
+ 'queued',
43
+ 'dispatching',
44
+ 'markdown_pending',
45
+ 'embedding_pending',
46
+ 'completed',
47
+ 'stale',
48
+ 'failed',
49
+ ];
50
+ if (!statuses.includes(normalized)) {
51
+ throw new CliError(`${label} is not a supported derivative rebuild status.`, {
52
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
53
+ exitCode: 1,
54
+ });
55
+ }
56
+ return normalized;
57
+ }
58
+ function responseBody(value, command) {
59
+ if (!isJsonObject(value) || value.ok !== true || value.command !== command) {
60
+ throw new CliError(`${command} returned an invalid response envelope.`, {
61
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
62
+ exitCode: 1,
63
+ details: value,
64
+ });
65
+ }
66
+ return value;
67
+ }
68
+ export function parseDerivativeSnapshotResponse(value, expected) {
69
+ const body = responseBody(value, 'cmd_dataset_derivative_rebuild_snapshot');
70
+ if (body.schema_version !== 'dataset-derivative-snapshot.v1' ||
71
+ body.table !== 'processes' ||
72
+ body.id !== expected.id ||
73
+ body.version !== expected.version ||
74
+ body.user_id !== expected.userId ||
75
+ body.state_code !== 0) {
76
+ throw new CliError('Derivative snapshot identity, owner, state, or schema is invalid.', {
77
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
78
+ exitCode: 1,
79
+ details: body,
80
+ });
81
+ }
82
+ const modifiedAt = nullableTimestamp(body.modified_at, 'Derivative snapshot modified_at');
83
+ if (!modifiedAt) {
84
+ throw new CliError('Derivative snapshot modified_at must be non-null.', {
85
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
86
+ exitCode: 1,
87
+ });
88
+ }
89
+ const jsonSha256 = requiredSha256(body.json_sha256, 'Derivative snapshot json_sha256');
90
+ const jsonOrderedSha256 = requiredSha256(body.json_ordered_sha256, 'Derivative snapshot json_ordered_sha256');
91
+ if (jsonSha256 !== jsonOrderedSha256) {
92
+ throw new CliError('Derivative snapshot json and json_ordered hashes must match.', {
93
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PRIMARY_MISMATCH',
94
+ exitCode: 1,
95
+ details: { json_sha256: jsonSha256, json_ordered_sha256: jsonOrderedSha256 },
96
+ });
97
+ }
98
+ return {
99
+ schema_version: 'dataset-derivative-snapshot.v1',
100
+ table: 'processes',
101
+ id: expected.id,
102
+ version: expected.version,
103
+ user_id: expected.userId,
104
+ state_code: 0,
105
+ modified_at: modifiedAt,
106
+ json_sha256: jsonSha256,
107
+ json_ordered_sha256: jsonOrderedSha256,
108
+ extracted_text_sha256: requiredSha256(body.extracted_text_sha256, 'Derivative snapshot extracted_text_sha256'),
109
+ extracted_md_sha256: nullableSha256(body.extracted_md_sha256, 'Derivative snapshot extracted_md_sha256'),
110
+ embedding_ft_sha256: nullableSha256(body.embedding_ft_sha256, 'Derivative snapshot embedding_ft_sha256'),
111
+ embedding_ft_at: nullableTimestamp(body.embedding_ft_at, 'Derivative snapshot embedding_ft_at'),
112
+ snapshot_sha256: requiredSha256(body.snapshot_sha256, 'Derivative snapshot snapshot_sha256'),
113
+ };
114
+ }
115
+ export function isDerivativeRebuildPlan(plan) {
116
+ return plan.operation === 'rebuild-derivatives';
117
+ }
118
+ export function derivativePlanAction(plan) {
119
+ const action = plan.actions[0];
120
+ if (!isDerivativeRebuildPlan(plan) ||
121
+ plan.target_mode !== 'owner_draft' ||
122
+ plan.actions.length !== 1 ||
123
+ !action ||
124
+ action.action !== 'rebuild_derivatives' ||
125
+ action.table !== 'processes' ||
126
+ !action.derivative_before) {
127
+ throw new CliError('Derivative rebuild plan does not match the fixed V1 contract.', {
128
+ code: 'DATASET_MAINTENANCE_PLAN_INVALID',
129
+ exitCode: 2,
130
+ });
131
+ }
132
+ return action;
133
+ }
134
+ export function buildDerivativePlanRequest(plan) {
135
+ const action = derivativePlanAction(plan);
136
+ const derivativeBefore = action.derivative_before;
137
+ return {
138
+ schema_version: 'dataset-derivative-rebuild-plan.v1',
139
+ plan_sha256: plan.plan_sha256,
140
+ operation_id: plan.operation_id,
141
+ target_visibility: 'owner_draft',
142
+ actions: [
143
+ {
144
+ action_id: action.action_id,
145
+ action: 'rebuild_derivatives',
146
+ table: 'processes',
147
+ id: action.id,
148
+ version: action.version,
149
+ expected_state_code: 0,
150
+ expected_snapshot_sha256: derivativeBefore.snapshot_sha256,
151
+ components: [...DERIVATIVE_REBUILD_COMPONENTS],
152
+ reason_code: action.reason_code,
153
+ },
154
+ ],
155
+ };
156
+ }
157
+ export function parseDerivativeSubmitResponse(value, plan) {
158
+ const body = responseBody(value, 'cmd_dataset_derivative_rebuild_plan_guarded');
159
+ const status = requestStatus(body.status, 'Derivative submit status');
160
+ if (body.schema_version !== 'dataset-derivative-rebuild-plan.v1' ||
161
+ body.plan_sha256 !== plan.plan_sha256 ||
162
+ body.operation_id !== plan.operation_id ||
163
+ body.target_visibility !== 'owner_draft' ||
164
+ body.action_count !== 1 ||
165
+ body.accepted_count !== 1 ||
166
+ typeof body.idempotent_replay !== 'boolean' ||
167
+ status !== 'queued') {
168
+ throw new CliError('Derivative submit response does not match the approved plan.', {
169
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
170
+ exitCode: 1,
171
+ details: body,
172
+ });
173
+ }
174
+ const summaryAuditId = requiredToken(body.summary_audit_id, 'Derivative summary_audit_id');
175
+ const databaseAuditId = requiredToken(body.database_audit_id, 'Derivative database_audit_id');
176
+ if (!POSITIVE_INTEGER_TEXT.test(summaryAuditId) || !POSITIVE_INTEGER_TEXT.test(databaseAuditId)) {
177
+ throw new CliError('Derivative audit ids must be numeric strings safe from JSON precision loss.', {
178
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
179
+ exitCode: 1,
180
+ });
181
+ }
182
+ return {
183
+ schema_version: 'dataset-derivative-rebuild-plan.v1',
184
+ plan_sha256: plan.plan_sha256,
185
+ operation_id: plan.operation_id,
186
+ target_visibility: 'owner_draft',
187
+ plan_request_sha256: requiredSha256(body.plan_request_sha256, 'Derivative plan_request_sha256'),
188
+ idempotent_replay: body.idempotent_replay,
189
+ action_count: 1,
190
+ accepted_count: 1,
191
+ summary_audit_id: summaryAuditId,
192
+ request_id: requiredToken(body.request_id, 'Derivative request_id'),
193
+ status,
194
+ action_request_sha256: requiredSha256(body.action_request_sha256, 'Derivative action_request_sha256'),
195
+ database_audit_id: databaseAuditId,
196
+ };
197
+ }
198
+ export function parseDerivativeStatusResponse(value, plan, submit) {
199
+ const action = derivativePlanAction(plan);
200
+ const body = responseBody(value, 'cmd_dataset_derivative_rebuild_read');
201
+ const status = requestStatus(body.status, 'Derivative read status');
202
+ const phase = requiredToken(body.phase, 'Derivative read phase');
203
+ if (body.schema_version !== 'dataset-derivative-rebuild-status.v1' ||
204
+ body.request_id !== submit.request_id ||
205
+ body.plan_sha256 !== plan.plan_sha256 ||
206
+ body.operation_id !== plan.operation_id ||
207
+ body.action_id !== action.action_id ||
208
+ body.table !== 'processes' ||
209
+ body.id !== action.id ||
210
+ body.version !== action.version ||
211
+ body.plan_request_sha256 !== submit.plan_request_sha256 ||
212
+ body.action_request_sha256 !== submit.action_request_sha256 ||
213
+ body.database_audit_id !== submit.database_audit_id ||
214
+ body.summary_audit_id !== submit.summary_audit_id) {
215
+ throw new CliError('Derivative status response does not match the accepted request proof.', {
216
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
217
+ exitCode: 1,
218
+ details: body,
219
+ });
220
+ }
221
+ const completedSnapshotSha256 = body.completed_snapshot_sha256 === null
222
+ ? null
223
+ : requiredSha256(body.completed_snapshot_sha256, 'Derivative completed_snapshot_sha256');
224
+ const completedAt = nullableTimestamp(body.completed_at, 'Derivative completed_at');
225
+ const error = body.error === null ? null : isJsonObject(body.error) ? body.error : undefined;
226
+ if (typeof body.fence_active !== 'boolean' ||
227
+ error === undefined ||
228
+ (status === 'completed' &&
229
+ (!completedSnapshotSha256 || !completedAt || error !== null || body.fence_active)) ||
230
+ (status !== 'completed' && (completedSnapshotSha256 !== null || completedAt !== null))) {
231
+ throw new CliError('Derivative status terminal proof is invalid.', {
232
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',
233
+ exitCode: 1,
234
+ details: body,
235
+ });
236
+ }
237
+ return {
238
+ schema_version: 'dataset-derivative-rebuild-status.v1',
239
+ request_id: submit.request_id,
240
+ plan_sha256: plan.plan_sha256,
241
+ operation_id: plan.operation_id,
242
+ action_id: action.action_id,
243
+ table: 'processes',
244
+ id: action.id,
245
+ version: action.version,
246
+ status,
247
+ phase,
248
+ fence_active: body.fence_active,
249
+ plan_request_sha256: submit.plan_request_sha256,
250
+ action_request_sha256: submit.action_request_sha256,
251
+ database_audit_id: submit.database_audit_id,
252
+ summary_audit_id: submit.summary_audit_id,
253
+ completed_snapshot_sha256: completedSnapshotSha256,
254
+ completed_at: completedAt,
255
+ error,
256
+ };
257
+ }
258
+ export function derivativeStatusCategory(status) {
259
+ if (status === 'completed')
260
+ return 'passed';
261
+ if (['stale', 'failed'].includes(status))
262
+ return 'failed';
263
+ return 'pending';
264
+ }
265
+ //# sourceMappingURL=dataset-maintenance-derivatives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-maintenance-derivatives.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-derivatives.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors.js';\nimport {\n DERIVATIVE_REBUILD_COMPONENTS,\n isJsonObject,\n type DatasetMaintenanceDerivativeSnapshot,\n type DatasetMaintenancePlan,\n type DatasetMaintenancePlanAction,\n type JsonObject,\n} from './dataset-maintenance-contract.js';\n\nexport type DatasetMaintenanceDerivativeRequestStatus =\n | 'queued'\n | 'dispatching'\n | 'markdown_pending'\n | 'embedding_pending'\n | 'completed'\n | 'stale'\n | 'failed';\n\nexport type DatasetMaintenanceDerivativeSubmitProof = {\n schema_version: 'dataset-derivative-rebuild-plan.v1';\n plan_sha256: string;\n operation_id: string;\n target_visibility: 'owner_draft';\n plan_request_sha256: string;\n idempotent_replay: boolean;\n action_count: 1;\n accepted_count: 1;\n summary_audit_id: string;\n request_id: string;\n status: DatasetMaintenanceDerivativeRequestStatus;\n action_request_sha256: string;\n database_audit_id: string;\n};\n\nexport type DatasetMaintenanceDerivativeStatusProof = {\n schema_version: 'dataset-derivative-rebuild-status.v1';\n request_id: string;\n plan_sha256: string;\n operation_id: string;\n action_id: string;\n table: 'processes';\n id: string;\n version: string;\n status: DatasetMaintenanceDerivativeRequestStatus;\n phase: string;\n fence_active: boolean;\n plan_request_sha256: string;\n action_request_sha256: string;\n database_audit_id: string;\n summary_audit_id: string;\n completed_snapshot_sha256: string | null;\n completed_at: string | null;\n error: JsonObject | null;\n};\n\nconst SHA256 = /^[a-f0-9]{64}$/u;\nconst POSITIVE_INTEGER_TEXT = /^[1-9]\\d*$/u;\n\nfunction requiredToken(value: unknown, label: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new CliError(`${label} must be a non-empty string.`, {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n });\n }\n return value.trim();\n}\n\nfunction requiredSha256(value: unknown, label: string): string {\n const normalized = requiredToken(value, label);\n if (!SHA256.test(normalized)) {\n throw new CliError(`${label} must be a lowercase SHA-256 digest.`, {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n });\n }\n return normalized;\n}\n\nfunction nullableSha256(value: unknown, label: string): string | null {\n return value === null ? null : requiredSha256(value, label);\n}\n\nfunction nullableTimestamp(value: unknown, label: string): string | null {\n if (value === null) return null;\n const normalized = requiredToken(value, label);\n if (!Number.isFinite(Date.parse(normalized))) {\n throw new CliError(`${label} must be an ISO timestamp or null.`, {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n });\n }\n return normalized;\n}\n\nfunction requestStatus(value: unknown, label: string): DatasetMaintenanceDerivativeRequestStatus {\n const normalized = requiredToken(value, label);\n const statuses: DatasetMaintenanceDerivativeRequestStatus[] = [\n 'queued',\n 'dispatching',\n 'markdown_pending',\n 'embedding_pending',\n 'completed',\n 'stale',\n 'failed',\n ];\n if (!statuses.includes(normalized as DatasetMaintenanceDerivativeRequestStatus)) {\n throw new CliError(`${label} is not a supported derivative rebuild status.`, {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n });\n }\n return normalized as DatasetMaintenanceDerivativeRequestStatus;\n}\n\nfunction responseBody(value: unknown, command: string): JsonObject {\n if (!isJsonObject(value) || value.ok !== true || value.command !== command) {\n throw new CliError(`${command} returned an invalid response envelope.`, {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n details: value,\n });\n }\n return value;\n}\n\nexport function parseDerivativeSnapshotResponse(\n value: unknown,\n expected: { id: string; version: string; userId: string },\n): DatasetMaintenanceDerivativeSnapshot {\n const body = responseBody(value, 'cmd_dataset_derivative_rebuild_snapshot');\n if (\n body.schema_version !== 'dataset-derivative-snapshot.v1' ||\n body.table !== 'processes' ||\n body.id !== expected.id ||\n body.version !== expected.version ||\n body.user_id !== expected.userId ||\n body.state_code !== 0\n ) {\n throw new CliError('Derivative snapshot identity, owner, state, or schema is invalid.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n details: body,\n });\n }\n const modifiedAt = nullableTimestamp(body.modified_at, 'Derivative snapshot modified_at');\n if (!modifiedAt) {\n throw new CliError('Derivative snapshot modified_at must be non-null.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n });\n }\n const jsonSha256 = requiredSha256(body.json_sha256, 'Derivative snapshot json_sha256');\n const jsonOrderedSha256 = requiredSha256(\n body.json_ordered_sha256,\n 'Derivative snapshot json_ordered_sha256',\n );\n if (jsonSha256 !== jsonOrderedSha256) {\n throw new CliError('Derivative snapshot json and json_ordered hashes must match.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PRIMARY_MISMATCH',\n exitCode: 1,\n details: { json_sha256: jsonSha256, json_ordered_sha256: jsonOrderedSha256 },\n });\n }\n return {\n schema_version: 'dataset-derivative-snapshot.v1',\n table: 'processes',\n id: expected.id,\n version: expected.version,\n user_id: expected.userId,\n state_code: 0,\n modified_at: modifiedAt,\n json_sha256: jsonSha256,\n json_ordered_sha256: jsonOrderedSha256,\n extracted_text_sha256: requiredSha256(\n body.extracted_text_sha256,\n 'Derivative snapshot extracted_text_sha256',\n ),\n extracted_md_sha256: nullableSha256(\n body.extracted_md_sha256,\n 'Derivative snapshot extracted_md_sha256',\n ),\n embedding_ft_sha256: nullableSha256(\n body.embedding_ft_sha256,\n 'Derivative snapshot embedding_ft_sha256',\n ),\n embedding_ft_at: nullableTimestamp(body.embedding_ft_at, 'Derivative snapshot embedding_ft_at'),\n snapshot_sha256: requiredSha256(body.snapshot_sha256, 'Derivative snapshot snapshot_sha256'),\n };\n}\n\nexport function isDerivativeRebuildPlan(plan: DatasetMaintenancePlan): boolean {\n return plan.operation === 'rebuild-derivatives';\n}\n\nexport function derivativePlanAction(plan: DatasetMaintenancePlan): DatasetMaintenancePlanAction {\n const action = plan.actions[0];\n if (\n !isDerivativeRebuildPlan(plan) ||\n plan.target_mode !== 'owner_draft' ||\n plan.actions.length !== 1 ||\n !action ||\n action.action !== 'rebuild_derivatives' ||\n action.table !== 'processes' ||\n !action.derivative_before\n ) {\n throw new CliError('Derivative rebuild plan does not match the fixed V1 contract.', {\n code: 'DATASET_MAINTENANCE_PLAN_INVALID',\n exitCode: 2,\n });\n }\n return action;\n}\n\nexport function buildDerivativePlanRequest(plan: DatasetMaintenancePlan): JsonObject {\n const action = derivativePlanAction(plan);\n const derivativeBefore = action.derivative_before!;\n return {\n schema_version: 'dataset-derivative-rebuild-plan.v1',\n plan_sha256: plan.plan_sha256,\n operation_id: plan.operation_id,\n target_visibility: 'owner_draft',\n actions: [\n {\n action_id: action.action_id,\n action: 'rebuild_derivatives',\n table: 'processes',\n id: action.id,\n version: action.version,\n expected_state_code: 0,\n expected_snapshot_sha256: derivativeBefore.snapshot_sha256,\n components: [...DERIVATIVE_REBUILD_COMPONENTS],\n reason_code: action.reason_code,\n },\n ],\n };\n}\n\nexport function parseDerivativeSubmitResponse(\n value: unknown,\n plan: DatasetMaintenancePlan,\n): DatasetMaintenanceDerivativeSubmitProof {\n const body = responseBody(value, 'cmd_dataset_derivative_rebuild_plan_guarded');\n const status = requestStatus(body.status, 'Derivative submit status');\n if (\n body.schema_version !== 'dataset-derivative-rebuild-plan.v1' ||\n body.plan_sha256 !== plan.plan_sha256 ||\n body.operation_id !== plan.operation_id ||\n body.target_visibility !== 'owner_draft' ||\n body.action_count !== 1 ||\n body.accepted_count !== 1 ||\n typeof body.idempotent_replay !== 'boolean' ||\n status !== 'queued'\n ) {\n throw new CliError('Derivative submit response does not match the approved plan.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n details: body,\n });\n }\n const summaryAuditId = requiredToken(body.summary_audit_id, 'Derivative summary_audit_id');\n const databaseAuditId = requiredToken(body.database_audit_id, 'Derivative database_audit_id');\n if (!POSITIVE_INTEGER_TEXT.test(summaryAuditId) || !POSITIVE_INTEGER_TEXT.test(databaseAuditId)) {\n throw new CliError(\n 'Derivative audit ids must be numeric strings safe from JSON precision loss.',\n {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n },\n );\n }\n return {\n schema_version: 'dataset-derivative-rebuild-plan.v1',\n plan_sha256: plan.plan_sha256,\n operation_id: plan.operation_id,\n target_visibility: 'owner_draft',\n plan_request_sha256: requiredSha256(body.plan_request_sha256, 'Derivative plan_request_sha256'),\n idempotent_replay: body.idempotent_replay as boolean,\n action_count: 1,\n accepted_count: 1,\n summary_audit_id: summaryAuditId,\n request_id: requiredToken(body.request_id, 'Derivative request_id'),\n status,\n action_request_sha256: requiredSha256(\n body.action_request_sha256,\n 'Derivative action_request_sha256',\n ),\n database_audit_id: databaseAuditId,\n };\n}\n\nexport function parseDerivativeStatusResponse(\n value: unknown,\n plan: DatasetMaintenancePlan,\n submit: DatasetMaintenanceDerivativeSubmitProof,\n): DatasetMaintenanceDerivativeStatusProof {\n const action = derivativePlanAction(plan);\n const body = responseBody(value, 'cmd_dataset_derivative_rebuild_read');\n const status = requestStatus(body.status, 'Derivative read status');\n const phase = requiredToken(body.phase, 'Derivative read phase');\n if (\n body.schema_version !== 'dataset-derivative-rebuild-status.v1' ||\n body.request_id !== submit.request_id ||\n body.plan_sha256 !== plan.plan_sha256 ||\n body.operation_id !== plan.operation_id ||\n body.action_id !== action.action_id ||\n body.table !== 'processes' ||\n body.id !== action.id ||\n body.version !== action.version ||\n body.plan_request_sha256 !== submit.plan_request_sha256 ||\n body.action_request_sha256 !== submit.action_request_sha256 ||\n body.database_audit_id !== submit.database_audit_id ||\n body.summary_audit_id !== submit.summary_audit_id\n ) {\n throw new CliError('Derivative status response does not match the accepted request proof.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n details: body,\n });\n }\n const completedSnapshotSha256 =\n body.completed_snapshot_sha256 === null\n ? null\n : requiredSha256(body.completed_snapshot_sha256, 'Derivative completed_snapshot_sha256');\n const completedAt = nullableTimestamp(body.completed_at, 'Derivative completed_at');\n const error = body.error === null ? null : isJsonObject(body.error) ? body.error : undefined;\n if (\n typeof body.fence_active !== 'boolean' ||\n error === undefined ||\n (status === 'completed' &&\n (!completedSnapshotSha256 || !completedAt || error !== null || body.fence_active)) ||\n (status !== 'completed' && (completedSnapshotSha256 !== null || completedAt !== null))\n ) {\n throw new CliError('Derivative status terminal proof is invalid.', {\n code: 'DATASET_MAINTENANCE_DERIVATIVE_PROOF_INVALID',\n exitCode: 1,\n details: body,\n });\n }\n return {\n schema_version: 'dataset-derivative-rebuild-status.v1',\n request_id: submit.request_id,\n plan_sha256: plan.plan_sha256,\n operation_id: plan.operation_id,\n action_id: action.action_id,\n table: 'processes',\n id: action.id,\n version: action.version,\n status,\n phase,\n fence_active: body.fence_active as boolean,\n plan_request_sha256: submit.plan_request_sha256,\n action_request_sha256: submit.action_request_sha256,\n database_audit_id: submit.database_audit_id,\n summary_audit_id: submit.summary_audit_id,\n completed_snapshot_sha256: completedSnapshotSha256,\n completed_at: completedAt,\n error,\n };\n}\n\nexport function derivativeStatusCategory(\n status: DatasetMaintenanceDerivativeRequestStatus,\n): 'pending' | 'passed' | 'failed' {\n if (status === 'completed') return 'passed';\n if (['stale', 'failed'].includes(status)) return 'failed';\n return 'pending';\n}\n"]}
@@ -0,0 +1,270 @@
1
+ import { CliError } from './errors.js';
2
+ function isRecord(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ }
5
+ function isIntegerBetween(value, minimum, maximum) {
6
+ return Number.isInteger(value) && Number(value) >= minimum && Number(value) <= maximum;
7
+ }
8
+ export function isDatasetMaintenanceSnapshotCompleteness(value, expectedTables) {
9
+ if (!isRecord(value))
10
+ return false;
11
+ if (value.status !== 'complete' ||
12
+ value.complete !== true ||
13
+ value.strategy !== 'postgrest_exact_count_multi_request' ||
14
+ !isIntegerBetween(value.requested_page_size, 1, 5_000) ||
15
+ !isIntegerBetween(value.page_count, expectedTables.length, Number.MAX_SAFE_INTEGER) ||
16
+ !isIntegerBetween(value.row_count, 0, Number.MAX_SAFE_INTEGER)) {
17
+ return false;
18
+ }
19
+ const entityCounts = value.entity_counts;
20
+ if (!isRecord(entityCounts))
21
+ return false;
22
+ const expectedTableSet = new Set(expectedTables);
23
+ const entityCountKeys = Object.keys(entityCounts);
24
+ if (entityCountKeys.length !== expectedTables.length ||
25
+ entityCountKeys.some((table) => !expectedTableSet.has(table))) {
26
+ return false;
27
+ }
28
+ for (const table of expectedTables) {
29
+ if (!isIntegerBetween(entityCounts[table], 0, Number.MAX_SAFE_INTEGER)) {
30
+ return false;
31
+ }
32
+ }
33
+ if (!Array.isArray(value.tables) || value.tables.length !== expectedTables.length)
34
+ return false;
35
+ const observedTables = new Set();
36
+ let pageCount = 0;
37
+ let rowCount = 0;
38
+ for (const rawTable of value.tables) {
39
+ if (!isRecord(rawTable))
40
+ return false;
41
+ const table = rawTable.table;
42
+ if (typeof table !== 'string' || !expectedTableSet.has(table) || observedTables.has(table)) {
43
+ return false;
44
+ }
45
+ if (rawTable.status !== 'complete' ||
46
+ rawTable.complete !== true ||
47
+ rawTable.strategy !== 'postgrest_exact_count' ||
48
+ rawTable.requested_page_size !== value.requested_page_size ||
49
+ !isIntegerBetween(rawTable.effective_page_size, 0, value.requested_page_size) ||
50
+ !isIntegerBetween(rawTable.pages_fetched, 1, Number.MAX_SAFE_INTEGER) ||
51
+ !isIntegerBetween(rawTable.rows_fetched, 0, Number.MAX_SAFE_INTEGER) ||
52
+ rawTable.exact_total !== rawTable.rows_fetched ||
53
+ (rawTable.rows_fetched === 0) !== (rawTable.effective_page_size === 0) ||
54
+ (rawTable.rows_fetched === 0
55
+ ? rawTable.pages_fetched !== 1
56
+ : rawTable.pages_fetched > rawTable.rows_fetched ||
57
+ rawTable.effective_page_size > rawTable.rows_fetched - rawTable.pages_fetched + 1 ||
58
+ rawTable.rows_fetched > rawTable.pages_fetched * rawTable.effective_page_size) ||
59
+ rawTable.termination_reason !== 'content_range_total_reached' ||
60
+ rawTable.content_range_verified !== true ||
61
+ rawTable.ordering_verified !== true ||
62
+ rawTable.duplicate_count !== 0 ||
63
+ rawTable.rows_fetched !== entityCounts[table]) {
64
+ return false;
65
+ }
66
+ observedTables.add(table);
67
+ pageCount += rawTable.pages_fetched;
68
+ rowCount += rawTable.rows_fetched;
69
+ }
70
+ return pageCount === value.page_count && rowCount === value.row_count;
71
+ }
72
+ export function isSnapshotCompletenessCompatible(value, baseline, expectedTables) {
73
+ if (value === undefined)
74
+ return baseline === undefined;
75
+ if (!isDatasetMaintenanceSnapshotCompleteness(value, expectedTables))
76
+ return false;
77
+ if (baseline === undefined)
78
+ return true;
79
+ return (value.row_count === baseline.row_count &&
80
+ expectedTables.every((table) => value.entity_counts[table] === baseline.entity_counts[table]));
81
+ }
82
+ function incomplete(details) {
83
+ throw new CliError('PostgREST pagination could not prove a complete maintenance snapshot.', {
84
+ code: 'DATASET_MAINTENANCE_SNAPSHOT_INCOMPLETE',
85
+ exitCode: 1,
86
+ details,
87
+ });
88
+ }
89
+ export function parseExactContentRange(value, details = {}) {
90
+ const match = value?.trim().match(/^(?:items\s+)?(?:(\d+)-(\d+)|\*)\/(\d+)$/iu);
91
+ if (!match) {
92
+ return incomplete({
93
+ ...details,
94
+ reason: 'content_range_missing_or_invalid',
95
+ content_range: value,
96
+ });
97
+ }
98
+ const start = match[1] === undefined ? null : Number.parseInt(match[1], 10);
99
+ const end = match[2] === undefined ? null : Number.parseInt(match[2], 10);
100
+ const total = Number.parseInt(match[3], 10);
101
+ if (!Number.isSafeInteger(total) ||
102
+ (start !== null &&
103
+ (end === null || !Number.isSafeInteger(start) || !Number.isSafeInteger(end))) ||
104
+ (start !== null && end < start)) {
105
+ return incomplete({ ...details, reason: 'content_range_out_of_bounds', content_range: value });
106
+ }
107
+ return { start, end, total };
108
+ }
109
+ export async function fetchCompletePostgrestPages(options) {
110
+ const rows = [];
111
+ const sourceUrls = [];
112
+ let expectedTotal = null;
113
+ let offset = 0;
114
+ let pagesFetched = 0;
115
+ let effectivePageSize = 0;
116
+ let previousIdentity = null;
117
+ while (true) {
118
+ const page = await options.fetchPage(offset);
119
+ pagesFetched += 1;
120
+ sourceUrls.push(page.source_url);
121
+ effectivePageSize = Math.max(effectivePageSize, page.rows.length);
122
+ const range = parseExactContentRange(page.content_range, {
123
+ table: options.table,
124
+ offset,
125
+ source_url: page.source_url,
126
+ });
127
+ if (expectedTotal === null) {
128
+ expectedTotal = range.total;
129
+ }
130
+ else if (range.total !== expectedTotal) {
131
+ return incomplete({
132
+ table: options.table,
133
+ offset,
134
+ source_url: page.source_url,
135
+ reason: 'exact_total_changed_between_pages',
136
+ expected_total: expectedTotal,
137
+ observed_total: range.total,
138
+ });
139
+ }
140
+ if (page.rows.length === 0) {
141
+ if (range.start !== null || offset !== 0 || range.total !== 0) {
142
+ return incomplete({
143
+ table: options.table,
144
+ offset,
145
+ source_url: page.source_url,
146
+ reason: 'empty_page_before_exact_total',
147
+ expected_total: expectedTotal,
148
+ content_range: page.content_range,
149
+ });
150
+ }
151
+ return {
152
+ rows,
153
+ source_urls: sourceUrls,
154
+ completeness: {
155
+ status: 'complete',
156
+ complete: true,
157
+ strategy: 'postgrest_exact_count',
158
+ requested_page_size: options.requestedPageSize,
159
+ effective_page_size: effectivePageSize,
160
+ pages_fetched: pagesFetched,
161
+ rows_fetched: 0,
162
+ exact_total: 0,
163
+ termination_reason: 'content_range_total_reached',
164
+ content_range_verified: true,
165
+ ordering_verified: true,
166
+ duplicate_count: 0,
167
+ },
168
+ };
169
+ }
170
+ if (page.rows.length > options.requestedPageSize ||
171
+ range.start !== offset ||
172
+ range.end !== offset + page.rows.length - 1) {
173
+ return incomplete({
174
+ table: options.table,
175
+ offset,
176
+ source_url: page.source_url,
177
+ reason: 'content_range_does_not_match_page',
178
+ requested_page_size: options.requestedPageSize,
179
+ returned_rows: page.rows.length,
180
+ content_range: page.content_range,
181
+ });
182
+ }
183
+ for (const row of page.rows) {
184
+ const identity = options.rowIdentity(row);
185
+ if (!identity) {
186
+ return incomplete({
187
+ table: options.table,
188
+ offset,
189
+ source_url: page.source_url,
190
+ reason: 'row_identity_missing',
191
+ });
192
+ }
193
+ if (previousIdentity !== null && identity <= previousIdentity) {
194
+ return incomplete({
195
+ table: options.table,
196
+ offset,
197
+ source_url: page.source_url,
198
+ reason: identity === previousIdentity ? 'duplicate_row_identity' : 'row_order_not_strict',
199
+ previous_identity: previousIdentity,
200
+ observed_identity: identity,
201
+ });
202
+ }
203
+ previousIdentity = identity;
204
+ }
205
+ rows.push(...page.rows);
206
+ const nextOffset = offset + page.rows.length;
207
+ if (nextOffset > expectedTotal) {
208
+ return incomplete({
209
+ table: options.table,
210
+ offset,
211
+ source_url: page.source_url,
212
+ reason: 'rows_exceed_exact_total',
213
+ expected_total: expectedTotal,
214
+ rows_fetched: nextOffset,
215
+ });
216
+ }
217
+ if (nextOffset === expectedTotal) {
218
+ return {
219
+ rows,
220
+ source_urls: sourceUrls,
221
+ completeness: {
222
+ status: 'complete',
223
+ complete: true,
224
+ strategy: 'postgrest_exact_count',
225
+ requested_page_size: options.requestedPageSize,
226
+ effective_page_size: effectivePageSize,
227
+ pages_fetched: pagesFetched,
228
+ rows_fetched: rows.length,
229
+ exact_total: expectedTotal,
230
+ termination_reason: 'content_range_total_reached',
231
+ content_range_verified: true,
232
+ ordering_verified: true,
233
+ duplicate_count: 0,
234
+ },
235
+ };
236
+ }
237
+ offset = nextOffset;
238
+ }
239
+ }
240
+ export function buildSnapshotCompleteness(options) {
241
+ const expectedTables = new Set(options.tables);
242
+ const observedTables = new Set(options.results.map((result) => result.table));
243
+ if (options.results.length !== options.tables.length ||
244
+ observedTables.size !== expectedTables.size ||
245
+ [...observedTables].some((table) => !expectedTables.has(table))) {
246
+ return incomplete({
247
+ reason: 'aggregate_table_set_invalid',
248
+ expected_tables: [...options.tables],
249
+ observed_tables: options.results.map((result) => result.table),
250
+ });
251
+ }
252
+ const entityCounts = Object.fromEntries(options.tables.map((table) => [
253
+ table,
254
+ options.results.find((result) => result.table === table).completeness.rows_fetched,
255
+ ]));
256
+ return {
257
+ status: 'complete',
258
+ complete: true,
259
+ strategy: 'postgrest_exact_count_multi_request',
260
+ requested_page_size: options.requestedPageSize,
261
+ page_count: options.results.reduce((sum, result) => sum + result.completeness.pages_fetched, 0),
262
+ row_count: options.results.reduce((sum, result) => sum + result.completeness.rows_fetched, 0),
263
+ entity_counts: entityCounts,
264
+ tables: options.results.map((result) => ({
265
+ table: result.table,
266
+ ...result.completeness,
267
+ })),
268
+ };
269
+ }
270
+ //# sourceMappingURL=dataset-maintenance-pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-maintenance-pagination.js","sourceRoot":"","sources":["../../../src/lib/dataset-maintenance-pagination.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors.js';\n\nexport type DatasetMaintenanceTableCompleteness = {\n status: 'complete';\n complete: true;\n strategy: 'postgrest_exact_count';\n requested_page_size: number;\n effective_page_size: number;\n pages_fetched: number;\n rows_fetched: number;\n exact_total: number;\n termination_reason: 'content_range_total_reached';\n content_range_verified: true;\n ordering_verified: true;\n duplicate_count: 0;\n};\n\nexport type DatasetMaintenanceSnapshotCompleteness<Table extends string = string> = {\n status: 'complete';\n complete: true;\n strategy: 'postgrest_exact_count_multi_request';\n requested_page_size: number;\n page_count: number;\n row_count: number;\n entity_counts: Record<Table, number>;\n tables: Array<{ table: Table } & DatasetMaintenanceTableCompleteness>;\n};\n\nexport type DatasetMaintenancePage<T> = {\n rows: T[];\n source_url: string;\n content_range: string | null;\n};\n\ntype ParsedContentRange = {\n start: number | null;\n end: number | null;\n total: number;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isIntegerBetween(value: unknown, minimum: number, maximum: number): value is number {\n return Number.isInteger(value) && Number(value) >= minimum && Number(value) <= maximum;\n}\n\nexport function isDatasetMaintenanceSnapshotCompleteness<Table extends string>(\n value: unknown,\n expectedTables: readonly Table[],\n): value is DatasetMaintenanceSnapshotCompleteness<Table> {\n if (!isRecord(value)) return false;\n if (\n value.status !== 'complete' ||\n value.complete !== true ||\n value.strategy !== 'postgrest_exact_count_multi_request' ||\n !isIntegerBetween(value.requested_page_size, 1, 5_000) ||\n !isIntegerBetween(value.page_count, expectedTables.length, Number.MAX_SAFE_INTEGER) ||\n !isIntegerBetween(value.row_count, 0, Number.MAX_SAFE_INTEGER)\n ) {\n return false;\n }\n\n const entityCounts = value.entity_counts;\n if (!isRecord(entityCounts)) return false;\n const expectedTableSet = new Set<string>(expectedTables);\n const entityCountKeys = Object.keys(entityCounts);\n if (\n entityCountKeys.length !== expectedTables.length ||\n entityCountKeys.some((table) => !expectedTableSet.has(table))\n ) {\n return false;\n }\n for (const table of expectedTables) {\n if (!isIntegerBetween(entityCounts[table], 0, Number.MAX_SAFE_INTEGER)) {\n return false;\n }\n }\n\n if (!Array.isArray(value.tables) || value.tables.length !== expectedTables.length) return false;\n const observedTables = new Set<string>();\n let pageCount = 0;\n let rowCount = 0;\n for (const rawTable of value.tables) {\n if (!isRecord(rawTable)) return false;\n const table = rawTable.table;\n if (typeof table !== 'string' || !expectedTableSet.has(table) || observedTables.has(table)) {\n return false;\n }\n if (\n rawTable.status !== 'complete' ||\n rawTable.complete !== true ||\n rawTable.strategy !== 'postgrest_exact_count' ||\n rawTable.requested_page_size !== value.requested_page_size ||\n !isIntegerBetween(rawTable.effective_page_size, 0, value.requested_page_size) ||\n !isIntegerBetween(rawTable.pages_fetched, 1, Number.MAX_SAFE_INTEGER) ||\n !isIntegerBetween(rawTable.rows_fetched, 0, Number.MAX_SAFE_INTEGER) ||\n rawTable.exact_total !== rawTable.rows_fetched ||\n (rawTable.rows_fetched === 0) !== (rawTable.effective_page_size === 0) ||\n (rawTable.rows_fetched === 0\n ? rawTable.pages_fetched !== 1\n : rawTable.pages_fetched > rawTable.rows_fetched ||\n rawTable.effective_page_size > rawTable.rows_fetched - rawTable.pages_fetched + 1 ||\n rawTable.rows_fetched > rawTable.pages_fetched * rawTable.effective_page_size) ||\n rawTable.termination_reason !== 'content_range_total_reached' ||\n rawTable.content_range_verified !== true ||\n rawTable.ordering_verified !== true ||\n rawTable.duplicate_count !== 0 ||\n rawTable.rows_fetched !== entityCounts[table]\n ) {\n return false;\n }\n observedTables.add(table);\n pageCount += rawTable.pages_fetched;\n rowCount += rawTable.rows_fetched;\n }\n\n return pageCount === value.page_count && rowCount === value.row_count;\n}\n\nexport function isSnapshotCompletenessCompatible<Table extends string>(\n value: unknown,\n baseline: DatasetMaintenanceSnapshotCompleteness<Table> | undefined,\n expectedTables: readonly Table[],\n): boolean {\n if (value === undefined) return baseline === undefined;\n if (!isDatasetMaintenanceSnapshotCompleteness(value, expectedTables)) return false;\n if (baseline === undefined) return true;\n return (\n value.row_count === baseline.row_count &&\n expectedTables.every((table) => value.entity_counts[table] === baseline.entity_counts[table])\n );\n}\n\nfunction incomplete(details: Record<string, unknown>): never {\n throw new CliError('PostgREST pagination could not prove a complete maintenance snapshot.', {\n code: 'DATASET_MAINTENANCE_SNAPSHOT_INCOMPLETE',\n exitCode: 1,\n details,\n });\n}\n\nexport function parseExactContentRange(\n value: string | null,\n details: Record<string, unknown> = {},\n): ParsedContentRange {\n const match = value?.trim().match(/^(?:items\\s+)?(?:(\\d+)-(\\d+)|\\*)\\/(\\d+)$/iu);\n if (!match) {\n return incomplete({\n ...details,\n reason: 'content_range_missing_or_invalid',\n content_range: value,\n });\n }\n const start = match[1] === undefined ? null : Number.parseInt(match[1], 10);\n const end = match[2] === undefined ? null : Number.parseInt(match[2], 10);\n const total = Number.parseInt(match[3]!, 10);\n if (\n !Number.isSafeInteger(total) ||\n (start !== null &&\n (end === null || !Number.isSafeInteger(start) || !Number.isSafeInteger(end))) ||\n (start !== null && end! < start)\n ) {\n return incomplete({ ...details, reason: 'content_range_out_of_bounds', content_range: value });\n }\n return { start, end, total };\n}\n\nexport async function fetchCompletePostgrestPages<T>(options: {\n table: string;\n requestedPageSize: number;\n fetchPage: (offset: number) => Promise<DatasetMaintenancePage<T>>;\n rowIdentity: (row: T) => string | null;\n}): Promise<{\n rows: T[];\n source_urls: string[];\n completeness: DatasetMaintenanceTableCompleteness;\n}> {\n const rows: T[] = [];\n const sourceUrls: string[] = [];\n let expectedTotal: number | null = null;\n let offset = 0;\n let pagesFetched = 0;\n let effectivePageSize = 0;\n let previousIdentity: string | null = null;\n\n while (true) {\n const page = await options.fetchPage(offset);\n pagesFetched += 1;\n sourceUrls.push(page.source_url);\n effectivePageSize = Math.max(effectivePageSize, page.rows.length);\n const range = parseExactContentRange(page.content_range, {\n table: options.table,\n offset,\n source_url: page.source_url,\n });\n if (expectedTotal === null) {\n expectedTotal = range.total;\n } else if (range.total !== expectedTotal) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: 'exact_total_changed_between_pages',\n expected_total: expectedTotal,\n observed_total: range.total,\n });\n }\n\n if (page.rows.length === 0) {\n if (range.start !== null || offset !== 0 || range.total !== 0) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: 'empty_page_before_exact_total',\n expected_total: expectedTotal,\n content_range: page.content_range,\n });\n }\n return {\n rows,\n source_urls: sourceUrls,\n completeness: {\n status: 'complete',\n complete: true,\n strategy: 'postgrest_exact_count',\n requested_page_size: options.requestedPageSize,\n effective_page_size: effectivePageSize,\n pages_fetched: pagesFetched,\n rows_fetched: 0,\n exact_total: 0,\n termination_reason: 'content_range_total_reached',\n content_range_verified: true,\n ordering_verified: true,\n duplicate_count: 0,\n },\n };\n }\n\n if (\n page.rows.length > options.requestedPageSize ||\n range.start !== offset ||\n range.end !== offset + page.rows.length - 1\n ) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: 'content_range_does_not_match_page',\n requested_page_size: options.requestedPageSize,\n returned_rows: page.rows.length,\n content_range: page.content_range,\n });\n }\n\n for (const row of page.rows) {\n const identity = options.rowIdentity(row);\n if (!identity) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: 'row_identity_missing',\n });\n }\n if (previousIdentity !== null && identity <= previousIdentity) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: identity === previousIdentity ? 'duplicate_row_identity' : 'row_order_not_strict',\n previous_identity: previousIdentity,\n observed_identity: identity,\n });\n }\n previousIdentity = identity;\n }\n\n rows.push(...page.rows);\n const nextOffset = offset + page.rows.length;\n if (nextOffset > expectedTotal) {\n return incomplete({\n table: options.table,\n offset,\n source_url: page.source_url,\n reason: 'rows_exceed_exact_total',\n expected_total: expectedTotal,\n rows_fetched: nextOffset,\n });\n }\n if (nextOffset === expectedTotal) {\n return {\n rows,\n source_urls: sourceUrls,\n completeness: {\n status: 'complete',\n complete: true,\n strategy: 'postgrest_exact_count',\n requested_page_size: options.requestedPageSize,\n effective_page_size: effectivePageSize,\n pages_fetched: pagesFetched,\n rows_fetched: rows.length,\n exact_total: expectedTotal,\n termination_reason: 'content_range_total_reached',\n content_range_verified: true,\n ordering_verified: true,\n duplicate_count: 0,\n },\n };\n }\n offset = nextOffset;\n }\n}\n\nexport function buildSnapshotCompleteness<Table extends string>(options: {\n tables: readonly Table[];\n requestedPageSize: number;\n results: Array<{ table: Table; completeness: DatasetMaintenanceTableCompleteness }>;\n}): DatasetMaintenanceSnapshotCompleteness<Table> {\n const expectedTables = new Set(options.tables);\n const observedTables = new Set(options.results.map((result) => result.table));\n if (\n options.results.length !== options.tables.length ||\n observedTables.size !== expectedTables.size ||\n [...observedTables].some((table) => !expectedTables.has(table))\n ) {\n return incomplete({\n reason: 'aggregate_table_set_invalid',\n expected_tables: [...options.tables],\n observed_tables: options.results.map((result) => result.table),\n });\n }\n const entityCounts = Object.fromEntries(\n options.tables.map((table) => [\n table,\n options.results.find((result) => result.table === table)!.completeness.rows_fetched,\n ]),\n ) as Record<Table, number>;\n return {\n status: 'complete',\n complete: true,\n strategy: 'postgrest_exact_count_multi_request',\n requested_page_size: options.requestedPageSize,\n page_count: options.results.reduce((sum, result) => sum + result.completeness.pages_fetched, 0),\n row_count: options.results.reduce((sum, result) => sum + result.completeness.rows_fetched, 0),\n entity_counts: entityCounts,\n tables: options.results.map((result) => ({\n table: result.table,\n ...result.completeness,\n })),\n };\n}\n"]}