@tiangong-lca/cli 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +18 -7
  2. package/assets/tidas-schemas/tidas_contacts.json +312 -0
  3. package/assets/tidas-schemas/tidas_contacts_category.json +126 -0
  4. package/assets/tidas-schemas/tidas_data_types.json +359 -0
  5. package/assets/tidas-schemas/tidas_flowproperties.json +303 -0
  6. package/assets/tidas-schemas/tidas_flowproperties_category.json +61 -0
  7. package/assets/tidas-schemas/tidas_flows.json +809 -0
  8. package/assets/tidas-schemas/tidas_flows_elementary_category.json +720 -0
  9. package/assets/tidas-schemas/tidas_flows_product_category.json +59623 -0
  10. package/assets/tidas-schemas/tidas_lciamethods.json +1326 -0
  11. package/assets/tidas-schemas/tidas_lciamethods_category.json +685 -0
  12. package/assets/tidas-schemas/tidas_lifecyclemodels.json +1374 -0
  13. package/assets/tidas-schemas/tidas_locations_category.json +2593 -0
  14. package/assets/tidas-schemas/tidas_processes.json +1646 -0
  15. package/assets/tidas-schemas/tidas_processes_category.json +10795 -0
  16. package/assets/tidas-schemas/tidas_sources.json +228 -0
  17. package/assets/tidas-schemas/tidas_sources_category.json +100 -0
  18. package/assets/tidas-schemas/tidas_unitgroups.json +338 -0
  19. package/assets/tidas-schemas/tidas_unitgroups_category.json +61 -0
  20. package/dist/src/cli.js +847 -13
  21. package/dist/src/cli.js.map +1 -1
  22. package/dist/src/lib/dataset-classification.js +947 -0
  23. package/dist/src/lib/dataset-classification.js.map +1 -0
  24. package/dist/src/lib/dataset-command.js +11 -0
  25. package/dist/src/lib/dataset-command.js.map +1 -1
  26. package/dist/src/lib/dataset-contract.js +2 -0
  27. package/dist/src/lib/dataset-contract.js.map +1 -1
  28. package/dist/src/lib/dataset-curation-queue.js +359 -2
  29. package/dist/src/lib/dataset-curation-queue.js.map +1 -1
  30. package/dist/src/lib/dataset-import-lca.js +18 -0
  31. package/dist/src/lib/dataset-import-lca.js.map +1 -1
  32. package/dist/src/lib/dataset-local.js +94 -1
  33. package/dist/src/lib/dataset-local.js.map +1 -1
  34. package/dist/src/lib/dataset-maintenance-clear-account.js +506 -0
  35. package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -0
  36. package/dist/src/lib/dataset-patch.js +797 -0
  37. package/dist/src/lib/dataset-patch.js.map +1 -0
  38. package/dist/src/lib/dataset-remote-verify.js +188 -3
  39. package/dist/src/lib/dataset-remote-verify.js.map +1 -1
  40. package/dist/src/lib/dataset-save-draft-run.js +725 -0
  41. package/dist/src/lib/dataset-save-draft-run.js.map +1 -0
  42. package/dist/src/lib/dataset-validate.js +32 -2
  43. package/dist/src/lib/dataset-validate.js.map +1 -1
  44. package/dist/src/lib/flow-publish-version.js +7 -2
  45. package/dist/src/lib/flow-publish-version.js.map +1 -1
  46. package/dist/src/lib/flow-qa.js +8 -0
  47. package/dist/src/lib/flow-qa.js.map +1 -1
  48. package/dist/src/lib/identity-preflight.js +895 -117
  49. package/dist/src/lib/identity-preflight.js.map +1 -1
  50. package/dist/src/lib/lifecyclemodel-qa.js +159 -34
  51. package/dist/src/lib/lifecyclemodel-qa.js.map +1 -1
  52. package/dist/src/lib/process-required-fields.js +62 -12
  53. package/dist/src/lib/process-required-fields.js.map +1 -1
  54. package/dist/src/lib/process-save-draft-run.js +10 -0
  55. package/dist/src/lib/process-save-draft-run.js.map +1 -1
  56. package/dist/src/lib/process-save-draft.js +59 -3
  57. package/dist/src/lib/process-save-draft.js.map +1 -1
  58. package/dist/src/lib/supabase-client.js +19 -8
  59. package/dist/src/lib/supabase-client.js.map +1 -1
  60. package/package.json +2 -1
@@ -0,0 +1,725 @@
1
+ import path from 'node:path';
2
+ import * as tidasSdk from '@tiangong-lca/tidas-sdk';
3
+ import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
4
+ import { collectImportContentIssues } from './dataset-validate.js';
5
+ import { createDatasetRecord, saveDraftDatasetRecord, } from './dataset-command.js';
6
+ import { readDatasetRowsInput } from './dataset-local.js';
7
+ import { CliError } from './errors.js';
8
+ import { normalizeIssuePath, validateSchemaWithDeepFallback, } from './tidas-sdk-validation.js';
9
+ import { collectProcessPlaceholderIssues, collectProcessRequiredFieldIssues, } from './process-required-fields.js';
10
+ import { buildDatasetCommandTransport } from './dataset-command.js';
11
+ import { createSupabaseDataClient, requireSupabaseRestRuntime, runSupabaseArrayQuery, } from './supabase-client.js';
12
+ import { createSupabaseDataRuntime } from './supabase-session.js';
13
+ import { collectRemoteReferences, lookupRemoteDataset, } from './dataset-remote-verify.js';
14
+ const DEFAULT_TIMEOUT_MS = 10_000;
15
+ function normalizeValidationIssue(issue) {
16
+ return {
17
+ path: normalizeIssuePath(issue.path ?? []),
18
+ message: issue.message ?? 'Validation failed',
19
+ code: issue.code ?? 'custom',
20
+ };
21
+ }
22
+ const DATASET_CONFIGS = {
23
+ contact: {
24
+ table: 'contacts',
25
+ rootKey: 'contactDataSet',
26
+ informationKey: 'contactInformation',
27
+ schemaName: 'ContactSchema',
28
+ factoryName: 'createContact',
29
+ },
30
+ source: {
31
+ table: 'sources',
32
+ rootKey: 'sourceDataSet',
33
+ informationKey: 'sourceInformation',
34
+ schemaName: 'SourceSchema',
35
+ factoryName: 'createSource',
36
+ },
37
+ unitgroup: {
38
+ table: 'unitgroups',
39
+ rootKey: 'unitGroupDataSet',
40
+ informationKey: 'unitGroupInformation',
41
+ schemaName: 'UnitGroupSchema',
42
+ factoryName: 'createUnitGroup',
43
+ },
44
+ flowproperty: {
45
+ table: 'flowproperties',
46
+ rootKey: 'flowPropertyDataSet',
47
+ informationKey: 'flowPropertiesInformation',
48
+ schemaName: 'FlowPropertySchema',
49
+ factoryName: 'createFlowProperty',
50
+ },
51
+ flow: {
52
+ table: 'flows',
53
+ rootKey: 'flowDataSet',
54
+ informationKey: 'flowInformation',
55
+ schemaName: 'FlowSchema',
56
+ factoryName: 'createFlow',
57
+ },
58
+ process: {
59
+ table: 'processes',
60
+ rootKey: 'processDataSet',
61
+ informationKey: 'processInformation',
62
+ schemaName: 'ProcessSchema',
63
+ factoryName: 'createProcess',
64
+ },
65
+ };
66
+ const REFERENCE_ONLY_SAVE_DRAFT_TYPES = new Set([
67
+ 'unitgroup',
68
+ 'flowproperty',
69
+ ]);
70
+ function isRecord(value) {
71
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
72
+ }
73
+ function trimToken(value) {
74
+ if (typeof value !== 'string') {
75
+ return null;
76
+ }
77
+ const trimmed = value.trim();
78
+ return trimmed || null;
79
+ }
80
+ function serializeError(error) {
81
+ if (error instanceof CliError) {
82
+ return { message: error.message, details: error.details };
83
+ }
84
+ if (error instanceof Error) {
85
+ return { message: error.message };
86
+ }
87
+ return { message: String(error) };
88
+ }
89
+ function normalizeType(value) {
90
+ const normalized = value?.trim().toLowerCase();
91
+ if (!normalized || normalized === 'auto') {
92
+ return 'auto';
93
+ }
94
+ if (normalized === 'contact' || normalized === 'contacts') {
95
+ return 'contact';
96
+ }
97
+ if (normalized === 'source' || normalized === 'sources') {
98
+ return 'source';
99
+ }
100
+ if (normalized === 'unitgroup' ||
101
+ normalized === 'unitgroups' ||
102
+ normalized === 'unit-group' ||
103
+ normalized === 'unit-groups') {
104
+ throw new CliError('Unit groups are reference-only support data for dataset save-draft. Select an existing database row instead of creating a custom My Data unit group.', {
105
+ code: 'DATASET_SAVE_DRAFT_REFERENCE_ONLY_TYPE',
106
+ exitCode: 2,
107
+ details: { type: normalized },
108
+ });
109
+ }
110
+ if (normalized === 'flowproperty' ||
111
+ normalized === 'flowproperties' ||
112
+ normalized === 'flow-property' ||
113
+ normalized === 'flow-properties') {
114
+ throw new CliError('Flow properties are reference-only support data for dataset save-draft. Select an existing database row instead of creating a custom My Data flow property.', {
115
+ code: 'DATASET_SAVE_DRAFT_REFERENCE_ONLY_TYPE',
116
+ exitCode: 2,
117
+ details: { type: normalized },
118
+ });
119
+ }
120
+ if (normalized === 'flow' || normalized === 'flows') {
121
+ return 'flow';
122
+ }
123
+ if (normalized === 'process' || normalized === 'processes') {
124
+ return 'process';
125
+ }
126
+ throw new CliError('Expected --type to be auto, contact, source, flow, or process.', {
127
+ code: 'DATASET_SAVE_DRAFT_TYPE_INVALID',
128
+ exitCode: 2,
129
+ details: value,
130
+ });
131
+ }
132
+ function unwrapPayload(row) {
133
+ for (const key of ['json_ordered', 'jsonOrdered', 'payload', 'json']) {
134
+ if (isRecord(row[key])) {
135
+ return row[key];
136
+ }
137
+ }
138
+ return row;
139
+ }
140
+ function detectType(payload) {
141
+ for (const [type, config] of Object.entries(DATASET_CONFIGS)) {
142
+ if (isRecord(payload[config.rootKey])) {
143
+ return type;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+ function schemaForConfig(config) {
149
+ const schema = tidasSdk[config.schemaName];
150
+ if (!schema ||
151
+ typeof schema !== 'object' ||
152
+ typeof schema.safeParse !== 'function') {
153
+ throw new CliError(`${String(config.schemaName)} is unavailable in @tiangong-lca/tidas-sdk.`, {
154
+ code: 'DATASET_SAVE_DRAFT_SCHEMA_UNAVAILABLE',
155
+ exitCode: 2,
156
+ details: { table: config.table },
157
+ });
158
+ }
159
+ const createEntity = tidasSdk[config.factoryName];
160
+ return {
161
+ schema: schema,
162
+ createEntity: typeof createEntity === 'function' ? createEntity : null,
163
+ };
164
+ }
165
+ function validatePayload(payload, type, config) {
166
+ const { schema, createEntity } = schemaForConfig(config);
167
+ const outcome = validateSchemaWithDeepFallback(schema, payload, createEntity);
168
+ const processIssues = type === 'process'
169
+ ? [...collectProcessRequiredFieldIssues(payload), ...collectProcessPlaceholderIssues(payload)]
170
+ : [];
171
+ const importIssues = type === 'process' ? [] : collectImportContentIssues(payload);
172
+ const issues = [
173
+ ...outcome.issues.map(normalizeValidationIssue),
174
+ ...processIssues,
175
+ ...importIssues,
176
+ ];
177
+ if (outcome.success && issues.length === 0) {
178
+ return {
179
+ ok: true,
180
+ validator: `@tiangong-lca/tidas-sdk/${String(config.schemaName)}+tiangong/import-content`,
181
+ issue_count: 0,
182
+ issues: [],
183
+ };
184
+ }
185
+ return {
186
+ ok: false,
187
+ validator: `@tiangong-lca/tidas-sdk/${String(config.schemaName)}+tiangong/import-content`,
188
+ issue_count: issues.length,
189
+ issues,
190
+ };
191
+ }
192
+ function extractIdentity(payload, row, config) {
193
+ const rootCandidate = payload[config.rootKey];
194
+ const root = isRecord(rootCandidate) ? rootCandidate : payload;
195
+ const informationCandidate = root[config.informationKey];
196
+ const information = isRecord(informationCandidate) ? informationCandidate : {};
197
+ const dataSetInformationCandidate = information.dataSetInformation;
198
+ const dataSetInformation = isRecord(dataSetInformationCandidate)
199
+ ? dataSetInformationCandidate
200
+ : {};
201
+ const administrativeInformation = isRecord(root.administrativeInformation)
202
+ ? root.administrativeInformation
203
+ : {};
204
+ const publicationAndOwnership = isRecord(administrativeInformation.publicationAndOwnership)
205
+ ? administrativeInformation.publicationAndOwnership
206
+ : {};
207
+ return {
208
+ id: trimToken(row.id) ?? trimToken(dataSetInformation['common:UUID']),
209
+ version: trimToken(row.version) ?? trimToken(publicationAndOwnership['common:dataSetVersion']) ?? null,
210
+ };
211
+ }
212
+ function flowType(payload) {
213
+ const rootCandidate = payload.flowDataSet;
214
+ const root = isRecord(rootCandidate) ? rootCandidate : payload;
215
+ const modellingCandidate = root.modellingAndValidation;
216
+ const modelling = isRecord(modellingCandidate) ? modellingCandidate : {};
217
+ const lciMethodCandidate = modelling.LCIMethod;
218
+ const lciMethod = isRecord(lciMethodCandidate) ? lciMethodCandidate : {};
219
+ return trimToken(lciMethod.typeOfDataSet);
220
+ }
221
+ function isElementaryFlowPayload(payload) {
222
+ return flowType(payload)?.trim().toLowerCase() === 'elementary flow';
223
+ }
224
+ function compareVersions(left, right) {
225
+ if (!left && !right) {
226
+ return 0;
227
+ }
228
+ if (!left) {
229
+ return -1;
230
+ }
231
+ if (!right) {
232
+ return 1;
233
+ }
234
+ const leftParts = left.split(/[._-]/u);
235
+ const rightParts = right.split(/[._-]/u);
236
+ const length = Math.max(leftParts.length, rightParts.length);
237
+ for (let index = 0; index < length; index += 1) {
238
+ const leftPart = leftParts[index] ?? '0';
239
+ const rightPart = rightParts[index] ?? '0';
240
+ const leftNumber = Number(leftPart);
241
+ const rightNumber = Number(rightPart);
242
+ if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
243
+ if (leftNumber !== rightNumber) {
244
+ return leftNumber > rightNumber ? 1 : -1;
245
+ }
246
+ }
247
+ else {
248
+ const compared = leftPart.localeCompare(rightPart);
249
+ if (compared !== 0) {
250
+ return compared > 0 ? 1 : -1;
251
+ }
252
+ }
253
+ }
254
+ return 0;
255
+ }
256
+ function supportLookupKey(reference) {
257
+ return `${reference.table}:${reference.id}:${reference.version ?? ''}`;
258
+ }
259
+ function isLookupableRemoteReference(reference) {
260
+ return Boolean(reference.table && reference.id);
261
+ }
262
+ function uniqueFlowRemoteReferences(payload) {
263
+ const references = new Map();
264
+ for (const reference of collectRemoteReferences([payload])) {
265
+ if (reference.role !== 'reference') {
266
+ continue;
267
+ }
268
+ if (reference.table && reference.id) {
269
+ references.set(supportLookupKey({
270
+ table: reference.table,
271
+ id: reference.id,
272
+ version: reference.version,
273
+ }), reference);
274
+ }
275
+ else {
276
+ references.set(remoteReferenceFallbackKey(reference), reference);
277
+ }
278
+ }
279
+ return [...references.values()];
280
+ }
281
+ function remoteReferenceFallbackKey(reference) {
282
+ return `${reference.path}:${reference.type ?? 'unknown'}`;
283
+ }
284
+ async function lookupCachedReferenceOnlySupport(options) {
285
+ const key = supportLookupKey(options.reference);
286
+ if (!options.cache.has(key)) {
287
+ options.cache.set(key, lookupRemoteDataset({
288
+ runtime: options.runtime,
289
+ fetchImpl: options.fetchImpl,
290
+ timeoutMs: options.timeoutMs,
291
+ request: {
292
+ table: options.reference.table,
293
+ id: options.reference.id,
294
+ version: options.reference.version,
295
+ },
296
+ }));
297
+ }
298
+ return options.cache.get(key);
299
+ }
300
+ async function missingFlowRemoteReferences(options) {
301
+ const missing = [];
302
+ for (const reference of uniqueFlowRemoteReferences(options.payload)) {
303
+ if (!isLookupableRemoteReference(reference)) {
304
+ missing.push({
305
+ table: reference.table,
306
+ id: reference.id,
307
+ version: reference.version,
308
+ path: reference.path,
309
+ short_description: reference.short_description,
310
+ status: 'unsupported_type',
311
+ latest_version: null,
312
+ });
313
+ continue;
314
+ }
315
+ if (!reference.version) {
316
+ missing.push({
317
+ table: reference.table,
318
+ id: reference.id,
319
+ version: null,
320
+ path: reference.path,
321
+ short_description: reference.short_description,
322
+ status: 'version_missing',
323
+ latest_version: null,
324
+ });
325
+ continue;
326
+ }
327
+ const lookup = await lookupCachedReferenceOnlySupport({ ...options, reference });
328
+ if (!lookup.latest) {
329
+ missing.push({
330
+ table: reference.table,
331
+ id: reference.id,
332
+ version: reference.version,
333
+ path: reference.path,
334
+ short_description: reference.short_description,
335
+ status: 'missing_dataset',
336
+ latest_version: null,
337
+ });
338
+ }
339
+ else if (!lookup.exact) {
340
+ missing.push({
341
+ table: reference.table,
342
+ id: reference.id,
343
+ version: reference.version,
344
+ path: reference.path,
345
+ short_description: reference.short_description,
346
+ status: 'missing_version',
347
+ latest_version: lookup.latest.version,
348
+ });
349
+ }
350
+ else if (compareVersions(lookup.latest.version, reference.version) > 0) {
351
+ missing.push({
352
+ table: reference.table,
353
+ id: reference.id,
354
+ version: reference.version,
355
+ path: reference.path,
356
+ short_description: reference.short_description,
357
+ status: 'version_outdated',
358
+ latest_version: lookup.latest.version,
359
+ });
360
+ }
361
+ }
362
+ return missing;
363
+ }
364
+ function prepareRows(inputPath, rawInput, requestedType) {
365
+ const rows = readDatasetRowsInput(inputPath, rawInput);
366
+ return rows.map((row, index) => {
367
+ const payload = unwrapPayload(row);
368
+ const type = requestedType === 'auto' ? detectType(payload) : requestedType;
369
+ const config = type ? DATASET_CONFIGS[type] : null;
370
+ const identity = config ? extractIdentity(payload, row, config) : { id: null, version: null };
371
+ return {
372
+ index,
373
+ row,
374
+ payload,
375
+ type,
376
+ config,
377
+ id: identity.id,
378
+ version: identity.version,
379
+ validation: config && type ? validatePayload(payload, type, config) : null,
380
+ };
381
+ });
382
+ }
383
+ function buildFiles(outDir) {
384
+ const outputDir = path.join(outDir, 'outputs', 'dataset-save-draft');
385
+ return {
386
+ selected_rows: path.join(outputDir, 'selected-rows.jsonl'),
387
+ progress_jsonl: path.join(outputDir, 'progress.jsonl'),
388
+ failures_jsonl: path.join(outputDir, 'failures.jsonl'),
389
+ summary_json: path.join(outputDir, 'summary.json'),
390
+ };
391
+ }
392
+ function defaultOutDir(inputPath, commit, now) {
393
+ const mode = commit ? 'commit' : 'dry-run';
394
+ const timestamp = now.toISOString().replace(/[:.]/gu, '').replace(/Z$/u, 'Z');
395
+ return path.join(path.dirname(inputPath), 'artifacts', 'dataset-save-draft', `${mode}-${timestamp}`);
396
+ }
397
+ function operationCount(rows) {
398
+ const counts = {};
399
+ for (const row of rows) {
400
+ const key = row.operation ?? 'none';
401
+ counts[key] = (counts[key] ?? 0) + 1;
402
+ }
403
+ return counts;
404
+ }
405
+ function byTable(rows) {
406
+ const counts = {};
407
+ for (const row of rows) {
408
+ if (row.config) {
409
+ counts[row.config.table] = (counts[row.config.table] ?? 0) + 1;
410
+ }
411
+ }
412
+ return counts;
413
+ }
414
+ function selectedRow(row) {
415
+ return {
416
+ index: row.index,
417
+ type: row.type,
418
+ table: row.config?.table ?? null,
419
+ id: row.id,
420
+ version: row.version,
421
+ validation: row.validation,
422
+ payload: row.payload,
423
+ };
424
+ }
425
+ function buildPreparedFailure(row) {
426
+ if (!row.type || !row.config) {
427
+ return {
428
+ index: row.index,
429
+ id: row.id,
430
+ version: row.version,
431
+ type: row.type,
432
+ table: null,
433
+ status: 'failed',
434
+ operation: 'type_unknown',
435
+ validation: null,
436
+ error: {
437
+ message: 'Could not detect dataset type. Use --type or provide a supported TIDAS wrapper.',
438
+ },
439
+ };
440
+ }
441
+ if (REFERENCE_ONLY_SAVE_DRAFT_TYPES.has(row.type)) {
442
+ return {
443
+ index: row.index,
444
+ id: row.id,
445
+ version: row.version,
446
+ type: row.type,
447
+ table: row.config.table,
448
+ status: 'failed',
449
+ operation: 'reference_only_type',
450
+ validation: row.validation,
451
+ error: {
452
+ message: 'Unit Groups and Flow Properties are reference-only support data. Rewrite references to existing database rows instead of writing these rows through dataset save-draft.',
453
+ },
454
+ };
455
+ }
456
+ if (!row.id || !row.version) {
457
+ return {
458
+ index: row.index,
459
+ id: row.id,
460
+ version: row.version,
461
+ type: row.type,
462
+ table: row.config.table,
463
+ status: 'failed',
464
+ operation: 'identity_missing',
465
+ validation: row.validation,
466
+ error: {
467
+ message: 'Dataset row is missing common:UUID or common:dataSetVersion required for save-draft.',
468
+ },
469
+ };
470
+ }
471
+ if (!row.validation?.ok) {
472
+ return {
473
+ index: row.index,
474
+ id: row.id,
475
+ version: row.version,
476
+ type: row.type,
477
+ table: row.config.table,
478
+ status: 'failed',
479
+ operation: 'skipped_invalid',
480
+ validation: row.validation,
481
+ error: {
482
+ message: `Local dataset validation failed with ${row.validation?.issue_count ?? 0} issue(s).`,
483
+ },
484
+ };
485
+ }
486
+ return null;
487
+ }
488
+ function buildVisibleRowsUrl(restBaseUrl, table, id, version) {
489
+ const url = new URL(`${restBaseUrl.replace(/\/+$/u, '')}/${table}`);
490
+ url.searchParams.set('select', 'id,version,user_id,state_code');
491
+ url.searchParams.set('id', `eq.${id}`);
492
+ url.searchParams.set('version', `eq.${version}`);
493
+ return url.toString();
494
+ }
495
+ function parseVisibleRows(payload, url) {
496
+ if (!Array.isArray(payload)) {
497
+ throw new CliError(`Supabase REST response was not a JSON array for ${url}`, {
498
+ code: 'SUPABASE_REST_RESPONSE_INVALID',
499
+ exitCode: 1,
500
+ details: payload,
501
+ });
502
+ }
503
+ return payload.map((item, index) => {
504
+ if (!isRecord(item)) {
505
+ throw new CliError(`Supabase REST row ${index} was not a JSON object for ${url}`, {
506
+ code: 'SUPABASE_REST_RESPONSE_INVALID',
507
+ exitCode: 1,
508
+ details: item,
509
+ });
510
+ }
511
+ return {
512
+ id: trimToken(item.id) ?? '',
513
+ version: trimToken(item.version) ?? '',
514
+ user_id: trimToken(item.user_id),
515
+ state_code: typeof item.state_code === 'number' ? item.state_code : null,
516
+ };
517
+ });
518
+ }
519
+ async function exactVisibleRows(options) {
520
+ const url = buildVisibleRowsUrl(options.restBaseUrl, options.table, options.id, options.version);
521
+ const payload = await runSupabaseArrayQuery(options.client
522
+ .from(options.table)
523
+ .select('id,version,user_id,state_code')
524
+ .eq('id', options.id)
525
+ .eq('version', options.version), url);
526
+ return parseVisibleRows(payload, url);
527
+ }
528
+ export async function runDatasetSaveDraft(options) {
529
+ const now = options.now ?? new Date();
530
+ const inputPath = path.resolve(options.inputPath);
531
+ const commit = options.commit === true;
532
+ const requestedType = normalizeType(options.type);
533
+ const outDir = path.resolve(options.outDir ?? defaultOutDir(inputPath, commit, now));
534
+ const files = buildFiles(outDir);
535
+ const preparedRows = prepareRows(inputPath, options.rawInput, requestedType);
536
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
537
+ if (commit && (!options.env || !options.fetchImpl)) {
538
+ throw new CliError('Dataset save-draft commit requires env and fetch runtime bindings.', {
539
+ code: 'DATASET_SAVE_DRAFT_RUNTIME_REQUIRED',
540
+ exitCode: 2,
541
+ });
542
+ }
543
+ writeJsonLinesArtifact(files.selected_rows, preparedRows.map(selectedRow));
544
+ const runtime = commit && options.env && options.fetchImpl
545
+ ? createSupabaseDataRuntime({
546
+ runtime: requireSupabaseRestRuntime(options.env),
547
+ fetchImpl: options.fetchImpl,
548
+ timeoutMs,
549
+ })
550
+ : null;
551
+ const commandTransport = runtime && options.fetchImpl
552
+ ? await buildDatasetCommandTransport({
553
+ runtime,
554
+ fetchImpl: options.fetchImpl,
555
+ timeoutMs,
556
+ })
557
+ : null;
558
+ const dataClient = runtime && options.fetchImpl
559
+ ? createSupabaseDataClient(runtime, options.fetchImpl, timeoutMs)
560
+ : null;
561
+ const referenceOnlySupportCache = new Map();
562
+ const reports = [];
563
+ for (const row of preparedRows) {
564
+ const preparedFailure = buildPreparedFailure(row);
565
+ if (preparedFailure) {
566
+ reports.push(preparedFailure);
567
+ continue;
568
+ }
569
+ const baseReport = {
570
+ index: row.index,
571
+ id: row.id,
572
+ version: row.version,
573
+ type: row.type,
574
+ table: row.config.table,
575
+ status: 'prepared',
576
+ operation: 'would_sync',
577
+ validation: row.validation,
578
+ };
579
+ if (!commit) {
580
+ reports.push(baseReport);
581
+ continue;
582
+ }
583
+ try {
584
+ const visibleRows = await exactVisibleRows({
585
+ client: dataClient.client,
586
+ restBaseUrl: dataClient.restBaseUrl,
587
+ table: row.config.table,
588
+ id: row.id,
589
+ version: row.version,
590
+ });
591
+ const visibleRow = visibleRows[0] ?? null;
592
+ if (row.type === 'flow') {
593
+ if (!visibleRow && isElementaryFlowPayload(row.payload)) {
594
+ reports.push({
595
+ ...baseReport,
596
+ status: 'failed',
597
+ operation: 'elementary_flow_insert_blocked',
598
+ visible_row: null,
599
+ error: {
600
+ message: 'Elementary flows are reference-only for dataset save-draft. Resolve the flow with remote hybrid search and reference the existing database row instead of creating a My Data flow.',
601
+ details: {
602
+ code: 'DATASET_SAVE_DRAFT_ELEMENTARY_FLOW_INSERT_BLOCKED',
603
+ },
604
+ },
605
+ });
606
+ continue;
607
+ }
608
+ const unresolvedReferences = await missingFlowRemoteReferences({
609
+ runtime: runtime,
610
+ fetchImpl: options.fetchImpl,
611
+ timeoutMs,
612
+ cache: referenceOnlySupportCache,
613
+ payload: row.payload,
614
+ });
615
+ if (unresolvedReferences.length > 0) {
616
+ reports.push({
617
+ ...baseReport,
618
+ status: 'failed',
619
+ operation: 'remote_reference_unresolved',
620
+ visible_row: visibleRow,
621
+ error: {
622
+ message: 'Flow save-draft commit requires all referenced datasets to already resolve in the remote database.',
623
+ details: {
624
+ code: 'DATASET_SAVE_DRAFT_REMOTE_REFERENCE_UNRESOLVED',
625
+ references: unresolvedReferences,
626
+ },
627
+ },
628
+ });
629
+ continue;
630
+ }
631
+ }
632
+ if (visibleRow) {
633
+ await saveDraftDatasetRecord({
634
+ transport: commandTransport,
635
+ table: row.config.table,
636
+ id: row.id,
637
+ version: row.version,
638
+ payload: row.payload,
639
+ extraData: { ruleVerification: true },
640
+ });
641
+ reports.push({
642
+ ...baseReport,
643
+ status: 'executed',
644
+ operation: 'save_draft',
645
+ visible_row: visibleRow,
646
+ });
647
+ }
648
+ else {
649
+ await createDatasetRecord({
650
+ transport: commandTransport,
651
+ table: row.config.table,
652
+ id: row.id,
653
+ payload: row.payload,
654
+ extraData: { ruleVerification: true },
655
+ });
656
+ reports.push({
657
+ ...baseReport,
658
+ status: 'executed',
659
+ operation: 'insert',
660
+ visible_row: null,
661
+ });
662
+ }
663
+ }
664
+ catch (error) {
665
+ reports.push({
666
+ ...baseReport,
667
+ status: 'failed',
668
+ error: serializeError(error),
669
+ });
670
+ }
671
+ }
672
+ const failures = reports.filter((row) => row.status === 'failed');
673
+ writeJsonLinesArtifact(files.progress_jsonl, reports);
674
+ writeJsonLinesArtifact(files.failures_jsonl, failures);
675
+ const report = {
676
+ schema_version: 1,
677
+ generated_at_utc: now.toISOString(),
678
+ input_path: inputPath,
679
+ requested_type: requestedType,
680
+ out_dir: outDir,
681
+ commit,
682
+ mode: commit ? 'commit' : 'dry_run',
683
+ status: failures.length > 0 ? 'completed_with_failures' : 'completed',
684
+ counts: {
685
+ selected: preparedRows.length,
686
+ prepared: reports.filter((row) => row.status === 'prepared').length,
687
+ executed: reports.filter((row) => row.status === 'executed').length,
688
+ failed: failures.length,
689
+ by_table: byTable(preparedRows),
690
+ operations: operationCount(reports),
691
+ },
692
+ files,
693
+ rows: reports,
694
+ };
695
+ writeJsonArtifact(files.summary_json, report);
696
+ return report;
697
+ }
698
+ export const __testInternals = {
699
+ DATASET_CONFIGS,
700
+ buildFiles,
701
+ buildPreparedFailure,
702
+ buildVisibleRowsUrl,
703
+ byTable,
704
+ compareVersions,
705
+ defaultOutDir,
706
+ detectType,
707
+ extractIdentity,
708
+ flowType,
709
+ isElementaryFlowPayload,
710
+ isLookupableRemoteReference,
711
+ missingFlowRemoteReferences,
712
+ normalizeValidationIssue,
713
+ normalizeType,
714
+ operationCount,
715
+ parseVisibleRows,
716
+ prepareRows,
717
+ remoteReferenceFallbackKey,
718
+ selectedRow,
719
+ serializeError,
720
+ supportLookupKey,
721
+ unwrapPayload,
722
+ uniqueFlowRemoteReferences,
723
+ validatePayload,
724
+ };
725
+ //# sourceMappingURL=dataset-save-draft-run.js.map