orizu 0.5.39 → 0.5.41
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 +77 -0
- package/dist/dataset-clone-workflow.d.ts +11 -0
- package/dist/dataset-clone-workflow.d.ts.map +1 -0
- package/dist/dataset-clone-workflow.js +100 -0
- package/dist/dataset-commands.d.ts +3 -1
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +107 -91
- package/dist/dataset-delete-workflow.d.ts +4 -0
- package/dist/dataset-delete-workflow.d.ts.map +1 -0
- package/dist/dataset-delete-workflow.js +75 -0
- package/dist/dataset-lock-workflow.d.ts +12 -0
- package/dist/dataset-lock-workflow.d.ts.map +1 -0
- package/dist/dataset-lock-workflow.js +76 -0
- package/dist/dataset-readme-workflow.d.ts +13 -0
- package/dist/dataset-readme-workflow.d.ts.map +1 -0
- package/dist/dataset-readme-workflow.js +82 -0
- package/dist/dataset-row-workflow.d.ts +14 -0
- package/dist/dataset-row-workflow.d.ts.map +1 -0
- package/dist/dataset-row-workflow.js +202 -0
- package/dist/dataset-split-reads.d.ts +28 -0
- package/dist/dataset-split-reads.d.ts.map +1 -0
- package/dist/dataset-split-reads.js +47 -0
- package/dist/dataset-transfer-upload.d.ts +2 -6
- package/dist/dataset-transfer-upload.d.ts.map +1 -1
- package/dist/dataset-transfer-upload.js +9 -2
- package/dist/dataset-upload-snapshot.d.ts.map +1 -1
- package/dist/dataset-upload-snapshot.js +7 -1
- package/dist/dataset-version-workflow.d.ts +24 -0
- package/dist/dataset-version-workflow.d.ts.map +1 -0
- package/dist/dataset-version-workflow.js +99 -0
- package/dist/feedback-cli.js +3 -3
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +29 -3
- package/dist/hosted-boot.d.ts +1 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +17 -0
- package/dist/hosted-harness-opencode.d.ts +5 -6
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +73 -59
- package/dist/hosted-loop-lifecycle.d.ts +6 -11
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
- package/dist/hosted-loop-lifecycle.js +6 -11
- package/dist/hosted-loop.d.ts +9 -0
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/instruction-delivery.d.ts +14 -0
- package/dist/instruction-delivery.d.ts.map +1 -0
- package/dist/instruction-delivery.js +103 -0
- package/dist/instruction-set-sync/index.d.ts +17 -0
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +44 -17
- package/dist/instruction-set-update/index.d.ts +2 -2
- package/dist/instruction-set-update/index.d.ts.map +1 -1
- package/dist/instruction-set-update/index.js +2 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +19 -27
- package/dist/json-response.d.ts +1 -0
- package/dist/json-response.d.ts.map +1 -1
- package/dist/json-response.js +7 -0
- package/dist/last-error-record.d.ts +4 -2
- package/dist/last-error-record.d.ts.map +1 -1
- package/dist/last-error-record.js +10 -68
- package/dist/last-error-workflow.d.ts +4 -0
- package/dist/last-error-workflow.d.ts.map +1 -0
- package/dist/last-error-workflow.js +78 -0
- package/dist/preview-runtime.d.ts +2 -2
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +37 -7
- package/dist/task-commands.d.ts.map +1 -1
- package/dist/task-commands.js +9 -16
- package/dist/task-export-workflow.d.ts +4 -0
- package/dist/task-export-workflow.d.ts.map +1 -0
- package/dist/task-export-workflow.js +46 -0
- package/dist/task-report-workflow.d.ts +47 -0
- package/dist/task-report-workflow.d.ts.map +1 -0
- package/dist/task-report-workflow.js +160 -0
- package/dist/workbench-cli.js +1 -1
- package/package.json +1 -1
- package/src/dataset-clone-workflow.ts +100 -0
- package/src/dataset-commands.ts +95 -142
- package/src/dataset-delete-workflow.ts +72 -0
- package/src/dataset-lock-workflow.ts +74 -0
- package/src/dataset-readme-workflow.ts +80 -0
- package/src/dataset-row-workflow.ts +248 -0
- package/src/dataset-split-reads.ts +43 -0
- package/src/dataset-transfer-upload.ts +12 -5
- package/src/dataset-upload-snapshot.ts +8 -1
- package/src/dataset-version-workflow.ts +105 -0
- package/src/feedback-cli.ts +4 -4
- package/src/help.ts +29 -3
- package/src/hosted-boot.ts +16 -1
- package/src/hosted-harness-opencode.ts +74 -72
- package/src/hosted-loop-lifecycle.ts +6 -11
- package/src/hosted-loop.ts +20 -10
- package/src/hosted-model-cache.d.mts +2 -0
- package/src/hosted-model-cache.mjs +53 -0
- package/src/index.ts +12 -2
- package/src/instruction-delivery.ts +123 -0
- package/src/instruction-set-sync/index.ts +61 -14
- package/src/instruction-set-update/index.ts +4 -3
- package/src/instruction-sets-cli.ts +19 -27
- package/src/json-response.ts +8 -0
- package/src/last-error-record.ts +11 -85
- package/src/last-error-workflow.ts +121 -0
- package/src/preview-runtime.ts +40 -10
- package/src/task-commands.ts +9 -43
- package/src/task-export-workflow.ts +56 -0
- package/src/task-report-workflow.ts +179 -0
- package/src/workbench-cli.ts +1 -1
- package/vendor/skills/orizu/references/cli-reference.md +34 -0
- package/vendor/skills/orizu/references/dataset-design.md +1 -1
- package/dist/dataset-edit-rows.d.ts +0 -10
- package/dist/dataset-edit-rows.d.ts.map +0 -1
- package/dist/dataset-edit-rows.js +0 -90
- package/src/dataset-edit-rows.ts +0 -124
package/README.md
CHANGED
|
@@ -336,6 +336,10 @@ orizu logout
|
|
|
336
336
|
<td>Download a dataset</td>
|
|
337
337
|
<td><code>orizu datasets download --dataset <datasetId> --format jsonl --out ./dataset.jsonl</code></td>
|
|
338
338
|
</tr>
|
|
339
|
+
<tr>
|
|
340
|
+
<td>Inspect saved splits and download validation rows</td>
|
|
341
|
+
<td><code>orizu datasets splits list <datasetVersionId> --json</code><br><code>orizu datasets splits show <splitSetId> --json</code><br><code>orizu datasets download --dataset-version <datasetVersionId> --split-set <splitSetId> --split validation --format jsonl --out ./validation.jsonl</code></td>
|
|
342
|
+
</tr>
|
|
339
343
|
<tr>
|
|
340
344
|
<td>Append dataset rows</td>
|
|
341
345
|
<td><code>orizu datasets append --dataset <datasetId> --file ./datasets/support-extra.jsonl</code></td>
|
|
@@ -497,6 +501,39 @@ or interactive selection. SIGINT/SIGTERM closes append's owned JSONL reader and
|
|
|
497
501
|
in-flight transport; retained interactive selection keeps ordinary process signal
|
|
498
502
|
termination. Append adds no transient write retries or write deduplication.
|
|
499
503
|
|
|
504
|
+
Dataset `edit-rows` and `delete-rows` use the same transfer deadline for each
|
|
505
|
+
request, including its response body. When a request times out, loses its
|
|
506
|
+
connection or gets a server error or a malformed reply, the message says the
|
|
507
|
+
change may already be applied (or removed): check those rows before retrying.
|
|
508
|
+
A refusal from the server (for example, a locked dataset) leaves `edit-rows`
|
|
509
|
+
with the usual retry advice, and reports `delete-rows` as refused.
|
|
510
|
+
SIGINT/SIGTERM aborts the request in flight and exits 130/143. Neither command
|
|
511
|
+
retries a write automatically.
|
|
512
|
+
|
|
513
|
+
Dataset `lock` uses the same transfer deadline for its request, including the
|
|
514
|
+
response body. After a timeout, a lost connection, a server error or a malformed
|
|
515
|
+
reply, the message says the dataset may already be locked: running `lock` again
|
|
516
|
+
is safe and shows its current state. A refusal from the server keeps its usual
|
|
517
|
+
message. Lock keeps ordinary process signal termination and never retries.
|
|
518
|
+
|
|
519
|
+
Dataset `delete` asks you to type the dataset id first, then uses the same
|
|
520
|
+
transfer deadline for its request, including the response body. After a
|
|
521
|
+
timeout, a lost connection, a server error or a malformed reply, the message
|
|
522
|
+
says the dataset may already be deleted: check with
|
|
523
|
+
`orizu datasets list --status all` before retrying. When tasks or app versions
|
|
524
|
+
still use the dataset, the refusal also names `orizu datasets archive <id>`.
|
|
525
|
+
Delete keeps ordinary process signal termination and never retries.
|
|
526
|
+
|
|
527
|
+
Dataset `readme set` uses the same transfer deadline for its request, including
|
|
528
|
+
the response body. Each save adds a new README version. After a timeout, a lost
|
|
529
|
+
connection, a server error or a malformed reply, the message says the README may
|
|
530
|
+
already be saved as a new version: check the dataset's README tab before saving
|
|
531
|
+
again, since another save would add a second version. When another save for the
|
|
532
|
+
same dataset happened at the same time, the message says yours was not saved
|
|
533
|
+
and to run the command again. A README file that cannot be read is named and
|
|
534
|
+
nothing is sent. `readme set` keeps ordinary process signal termination and
|
|
535
|
+
never retries.
|
|
536
|
+
|
|
500
537
|
|
|
501
538
|
## Interactive And Automated Usage
|
|
502
539
|
|
|
@@ -514,3 +551,43 @@ Use `--json` on supported task and app commands when automation needs structured
|
|
|
514
551
|
- [skills/orizu/references/cli-reference.md](skills/orizu/references/cli-reference.md): compact command matrix and end-to-end flows
|
|
515
552
|
|
|
516
553
|
If the server loses or cannot decode a database append acknowledgement, it preserves the request's prepared row artifacts because rows may already reference them. Unknown outcomes require inspection; automatic orphan reconciliation and write deduplication are not promised. Known SQL refusals clean up only that request's newly prepared artifacts.
|
|
554
|
+
|
|
555
|
+
### Enrich validation rows while preserving split assignments
|
|
556
|
+
|
|
557
|
+
Start with the immutable dataset version ID returned by dataset upload or version
|
|
558
|
+
creation. List its saved split sets, then inspect one set to find the exact
|
|
559
|
+
partition name (custom names and empty partitions are retained):
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
orizu datasets splits list DATASET_VERSION_ID --json
|
|
563
|
+
orizu datasets splits show SPLIT_SET_ID --json > split.json
|
|
564
|
+
orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split validation --format jsonl --out validation.jsonl
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
`validation.jsonl` contains only that partition, in its saved order. Each row has
|
|
568
|
+
its canonical `id` and the fields stored in that version. These reads stay fixed
|
|
569
|
+
when the live dataset changes. No prompt, runner, scorer or optimization is needed.
|
|
570
|
+
|
|
571
|
+
The assignment JSON retains set and partition metadata, including arrays, scalars
|
|
572
|
+
and null values, together with exact saved memberships.
|
|
573
|
+
|
|
574
|
+
Enrich the exported rows in your own workflow, keeping every `id` unchanged.
|
|
575
|
+
Combine them with the other rows when preparing the complete enriched dataset.
|
|
576
|
+
After uploading that dataset or creating its version, use the new version's ID
|
|
577
|
+
explicitly to reuse every original assignment:
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
orizu datasets splits create TARGET_DATASET_VERSION_ID --from-file split.json --json
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
The import accepts the `name`, `strategy`, `seed`, `metadata` and
|
|
584
|
+
`partitions: [{name, row_ids}]` from show JSON. Source identity is for inspection;
|
|
585
|
+
the command's target version decides where assignments are created. Every
|
|
586
|
+
referenced canonical row ID must exist in that target. Missing IDs fail instead
|
|
587
|
+
of being dropped or remapped. Unknown or inaccessible versions, sets or names,
|
|
588
|
+
mismatched selectors and incomplete artifacts fail; pinned reads never fall back
|
|
589
|
+
to live data. Existing live downloads remain available with `--dataset`.
|
|
590
|
+
|
|
591
|
+
For saved split names beginning with `--`, use the unambiguous equals spelling:
|
|
592
|
+
`orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split=--validation --format jsonl --out validation.jsonl`.
|
|
593
|
+
The same spelling supports `--split=--json` and `--split=--help`; all pinned selectors remain required.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
declare const ClonedDataset: Schema.Struct<{
|
|
3
|
+
readonly id: Schema.String;
|
|
4
|
+
readonly name: Schema.String;
|
|
5
|
+
readonly rowCount: Schema.Number;
|
|
6
|
+
readonly parentDatasetId: Schema.String;
|
|
7
|
+
}>;
|
|
8
|
+
export type ClonedDataset = typeof ClonedDataset.Type;
|
|
9
|
+
export declare function cloneDataset(datasetId: string, name: string | null): Promise<ClonedDataset>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=dataset-clone-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-clone-workflow.d.ts","sourceRoot":"","sources":["../src/dataset-clone-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,MAAM,EAAE,MAAM,QAAQ,CAAA;AAU5D,QAAA,MAAM,aAAa;;;;;EAKjB,CAAA;AAGF,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAA;AA6ErD,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAIjG"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { authedFetch } from './http.js';
|
|
4
|
+
import { parseJsonResponse } from './json-response.js';
|
|
5
|
+
// Loaded only through `await import(...)` from dataset-commands, like lock.
|
|
6
|
+
const SERVER_MAY_BE_CREATED = 'Dataset clone may already be created';
|
|
7
|
+
const ClonedDataset = Schema.Struct({
|
|
8
|
+
id: Schema.String,
|
|
9
|
+
name: Schema.String,
|
|
10
|
+
rowCount: Schema.Number,
|
|
11
|
+
parentDatasetId: Schema.String,
|
|
12
|
+
});
|
|
13
|
+
const CloneReply = Schema.Struct({ dataset: ClonedDataset });
|
|
14
|
+
const ErrorReply = Schema.Struct({ error: Schema.String });
|
|
15
|
+
// A refusal means nothing was cloned. After any other failure once the request
|
|
16
|
+
// was sent, the clone may exist. Unlike lock, cloning again is not harmless: it
|
|
17
|
+
// makes another copy, so the clone is never retried and the user is told to look.
|
|
18
|
+
class CloneFailure extends Error {
|
|
19
|
+
mayBeCreated;
|
|
20
|
+
constructor(message, mayBeCreated) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.mayBeCreated = mayBeCreated;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mayBeCreatedGuidance(name) {
|
|
26
|
+
const target = name ? `a dataset named ${JSON.stringify(name)}` : 'a new dataset named after the source with " Copy" at the end';
|
|
27
|
+
return `The clone may already have been created. Run \`orizu datasets list\` and look for ${target} before cloning again: running clone again makes another copy.`;
|
|
28
|
+
}
|
|
29
|
+
// authedFetch raises this code only when it sent nothing, or when the server
|
|
30
|
+
// answered 401 and the changed credentials stopped a replay. Nothing was cloned.
|
|
31
|
+
function isAuthContextChange(cause) {
|
|
32
|
+
return cause instanceof Error && cause.message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:');
|
|
33
|
+
}
|
|
34
|
+
function serverError(text) {
|
|
35
|
+
try {
|
|
36
|
+
const decoded = Schema.decodeUnknownResult(ErrorReply)(JSON.parse(text));
|
|
37
|
+
return Result.isSuccess(decoded) ? decoded.success.error : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function cloneRequest(datasetId, name, timeoutMs) {
|
|
44
|
+
return Effect.suspend(() => {
|
|
45
|
+
let sent = false;
|
|
46
|
+
let timedOut = false;
|
|
47
|
+
return Effect.tryPromise({
|
|
48
|
+
try: async (signal) => {
|
|
49
|
+
const request = new AbortController();
|
|
50
|
+
const abort = () => request.abort();
|
|
51
|
+
const timer = setTimeout(() => { timedOut = true; request.abort(); }, timeoutMs);
|
|
52
|
+
if (signal.aborted)
|
|
53
|
+
abort();
|
|
54
|
+
else
|
|
55
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
56
|
+
try {
|
|
57
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/clone`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify(name ? { name } : {}),
|
|
61
|
+
signal: request.signal,
|
|
62
|
+
}, () => { sent = true; });
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
const error = serverError(text);
|
|
66
|
+
// The app's own 5xx bodies name what happened; anything else at 5xx
|
|
67
|
+
// (a gateway page, an empty body) says nothing about the clone.
|
|
68
|
+
const mayBeCreated = response.status < 400 || error === SERVER_MAY_BE_CREATED || (response.status >= 500 && error === null);
|
|
69
|
+
throw new CloneFailure(`Clone failed: ${text}`, mayBeCreated);
|
|
70
|
+
}
|
|
71
|
+
const data = await parseJsonResponse(response, 'Dataset clone');
|
|
72
|
+
const decoded = Schema.decodeUnknownResult(CloneReply)(data);
|
|
73
|
+
if (Result.isFailure(decoded)) {
|
|
74
|
+
throw new CloneFailure(`Dataset clone returned an unexpected response (status ${response.status}).`, true);
|
|
75
|
+
}
|
|
76
|
+
return decoded.success.dataset;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
signal.removeEventListener('abort', abort);
|
|
81
|
+
request.abort();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
catch: cause => {
|
|
85
|
+
if (timedOut)
|
|
86
|
+
return new CloneFailure('DATASET_TRANSFER_TIMEOUT: Dataset clone exceeded its deadline', true);
|
|
87
|
+
if (cause instanceof CloneFailure)
|
|
88
|
+
return cause;
|
|
89
|
+
return new CloneFailure(cause instanceof Error ? cause.message : String(cause), sent && !isAuthContextChange(cause));
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}).pipe(Effect.mapError(failure => new Error(failure.mayBeCreated ? `${failure.message}\n${mayBeCreatedGuidance(name)}` : failure.message)));
|
|
93
|
+
}
|
|
94
|
+
// Nothing to clean up, so signals keep Node's default handling.
|
|
95
|
+
export async function cloneDataset(datasetId, name) {
|
|
96
|
+
const exit = await Effect.runPromiseExit(cloneRequest(datasetId, name, datasetTransferTimeoutMs()));
|
|
97
|
+
if (Exit.isSuccess(exit))
|
|
98
|
+
return exit.value;
|
|
99
|
+
throw Cause.squash(exit.cause);
|
|
100
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
declare function createDatasetVersion(): Promise<void>;
|
|
2
|
+
declare function listDatasetSplitSets(): Promise<void>;
|
|
3
|
+
declare function showDatasetSplitSet(): Promise<void>;
|
|
2
4
|
declare function createDatasetSplitSet(): Promise<void>;
|
|
3
5
|
declare function listDatasets(): Promise<void>;
|
|
4
6
|
declare function uploadDataset(): Promise<void>;
|
|
@@ -12,5 +14,5 @@ declare function deleteDataset(): Promise<void>;
|
|
|
12
14
|
declare function lockDataset(): Promise<void>;
|
|
13
15
|
declare function cloneDataset(): Promise<void>;
|
|
14
16
|
declare function downloadAnnotations(): Promise<void>;
|
|
15
|
-
export { uploadDataset, listDatasets, pushDataset, downloadDataset, appendDatasetRows, setDatasetReadme, createDatasetVersion, createDatasetSplitSet, editDatasetRows, deleteDatasetRows, deleteDataset, lockDataset, cloneDataset, downloadAnnotations, };
|
|
17
|
+
export { uploadDataset, listDatasets, pushDataset, downloadDataset, appendDatasetRows, setDatasetReadme, createDatasetVersion, createDatasetSplitSet, listDatasetSplitSets, showDatasetSplitSet, editDatasetRows, deleteDatasetRows, deleteDataset, lockDataset, cloneDataset, downloadAnnotations, };
|
|
16
18
|
//# sourceMappingURL=dataset-commands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-commands.d.ts","sourceRoot":"","sources":["../src/dataset-commands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-commands.d.ts","sourceRoot":"","sources":["../src/dataset-commands.ts"],"names":[],"mappings":"AAuHA,iBAAe,oBAAoB,kBAkClC;AAED,iBAAe,oBAAoB,kBAYlC;AAED,iBAAe,mBAAmB,kBAcjC;AAED,iBAAe,qBAAqB,kBAkDnC;AAED,iBAAe,YAAY,kBAW1B;AAsBD,iBAAe,aAAa,kBAiC3B;AAED,iBAAe,WAAW,kBAwCzB;AAED,iBAAe,gBAAgB,kBA+B9B;AAgBD,iBAAe,eAAe,kBA6C7B;AAED,iBAAe,iBAAiB,kBAU/B;AAED,iBAAe,eAAe,kBA2B7B;AAED,iBAAe,iBAAiB,kBA2B/B;AA0BD,iBAAe,aAAa,kBAuB3B;AAED,iBAAe,WAAW,kBAuBzB;AAED,iBAAe,YAAY,kBAuB1B;AAED,iBAAe,mBAAmB,kBA0BjC;AAED,OAAO,EACL,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,YAAY,EACZ,mBAAmB,GACpB,CAAA"}
|
package/dist/dataset-commands.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
// ORI-2044: moved from index.ts to keep dataset commands together.
|
|
2
|
-
import { readFileSync
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
3
|
import { basename, extname } from 'path';
|
|
4
4
|
import { createInterface } from 'readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'process';
|
|
6
|
-
import { getArg, expandHomePath, getPositionalArg, hasJsonFlag, getArchiveListStatus, cliArgs, isInteractiveTerminal, } from './command-line-arguments.js';
|
|
6
|
+
import { getArg, hasArg, expandHomePath, getPositionalArg, hasJsonFlag, getArchiveListStatus, cliArgs, isInteractiveTerminal, } from './command-line-arguments.js';
|
|
7
7
|
import { resolveProjectSlug, fetchDatasets, selectDatasetInteractively, selectTaskIdInteractively, } from './account-directory.js';
|
|
8
8
|
import { sanitizeTerminalText, parseJsonResponse } from './json-response.js';
|
|
9
9
|
import { authedFetch } from './http.js';
|
|
10
|
-
import { extractErrorMessage } from './error-response.js';
|
|
11
10
|
import { printJson, printLine } from './cli-console-output.js';
|
|
12
11
|
import { readJsonFile } from './app-commands.js';
|
|
13
12
|
import { printDatasetSummaries } from './archive-artifacts-cli.js';
|
|
14
13
|
import { formatTerminalLink } from './auth-commands.js';
|
|
15
14
|
import { parseDatasetFile } from './file-parser.js';
|
|
16
15
|
import { parseDatasetReference } from './dataset-download.js';
|
|
17
|
-
import { editDatasetRows as runEditDatasetRows } from './dataset-edit-rows.js';
|
|
18
16
|
import { parseCommaSeparated } from './task-commands.js';
|
|
19
17
|
const MAX_README_LENGTH = 200_000;
|
|
18
|
+
function readReadmeFile(readmeFile) {
|
|
19
|
+
try {
|
|
20
|
+
return readFileSync(expandHomePath(readmeFile), 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const missing = error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
24
|
+
const reason = missing ? 'file not found' : error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new Error(`Could not read README file ${readmeFile}: ${reason}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
20
28
|
function readReadmeMarkdownFromArgs(usage) {
|
|
21
29
|
const readmeFile = getArg('--readme-file');
|
|
22
30
|
const readmeText = getArg('--readme-text');
|
|
@@ -24,7 +32,7 @@ function readReadmeMarkdownFromArgs(usage) {
|
|
|
24
32
|
throw new Error(`Use either --readme-file or --readme-text, not both.\n${usage}`);
|
|
25
33
|
}
|
|
26
34
|
const markdown = readmeFile !== null
|
|
27
|
-
?
|
|
35
|
+
? readReadmeFile(readmeFile)
|
|
28
36
|
: readmeText;
|
|
29
37
|
if (markdown !== null && markdown.length > MAX_README_LENGTH) {
|
|
30
38
|
throw new Error(`README markdown is too large. Maximum size is ${MAX_README_LENGTH} characters.`);
|
|
@@ -72,22 +80,6 @@ async function resolveDatasetIdForProject(datasetRef, projectArg) {
|
|
|
72
80
|
}
|
|
73
81
|
return matches[0].id;
|
|
74
82
|
}
|
|
75
|
-
async function requestDatasetVersionSnapshot(datasetId, options = {}) {
|
|
76
|
-
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/versions`, {
|
|
77
|
-
method: 'POST',
|
|
78
|
-
headers: { 'Content-Type': 'application/json' },
|
|
79
|
-
body: JSON.stringify({
|
|
80
|
-
...(options.versionLabel ? { versionLabel: options.versionLabel } : {}),
|
|
81
|
-
...(options.readmeMarkdown !== undefined && options.readmeMarkdown !== null
|
|
82
|
-
? { readmeMarkdown: options.readmeMarkdown }
|
|
83
|
-
: {}),
|
|
84
|
-
}),
|
|
85
|
-
});
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
throw new Error(`Failed to create dataset version: ${await extractErrorMessage(response)}`);
|
|
88
|
-
}
|
|
89
|
-
return parseJsonResponse(response, 'Dataset version create');
|
|
90
|
-
}
|
|
91
83
|
async function createDatasetVersion() {
|
|
92
84
|
const datasetId = getPositionalArg(3) || getArg('--dataset');
|
|
93
85
|
const versionLabel = getArg('--label') || getArg('--version-label') || null;
|
|
@@ -98,7 +90,8 @@ async function createDatasetVersion() {
|
|
|
98
90
|
}
|
|
99
91
|
const readmeMarkdown = readReadmeMarkdownFromArgs(usage);
|
|
100
92
|
const resolvedDatasetId = await resolveDatasetIdForProject(datasetId, project);
|
|
101
|
-
const
|
|
93
|
+
const { createDatasetVersionSnapshot } = await import('./dataset-version-workflow.js');
|
|
94
|
+
const data = await createDatasetVersionSnapshot(resolvedDatasetId, {
|
|
102
95
|
versionLabel,
|
|
103
96
|
readmeMarkdown,
|
|
104
97
|
});
|
|
@@ -116,6 +109,41 @@ async function createDatasetVersion() {
|
|
|
116
109
|
printLine(`Created dataset version ${sanitizeTerminalText(data.datasetVersion.id)}` +
|
|
117
110
|
(details ? ` (${sanitizeTerminalText(details)})` : ''));
|
|
118
111
|
}
|
|
112
|
+
async function listDatasetSplitSets() {
|
|
113
|
+
const versionId = getPositionalArg(3);
|
|
114
|
+
if (!versionId)
|
|
115
|
+
throw new Error('Usage: orizu datasets splits list <datasetVersionId> [--json]');
|
|
116
|
+
const { listDatasetSplits } = await import('./dataset-split-reads.js');
|
|
117
|
+
const data = await listDatasetSplits(versionId);
|
|
118
|
+
if (hasJsonFlag())
|
|
119
|
+
return printJson({ ...data });
|
|
120
|
+
printLine(`Dataset version ${sanitizeTerminalText(data.dataset_version_id)}`);
|
|
121
|
+
for (const set of data.split_sets) {
|
|
122
|
+
printLine(`${sanitizeTerminalText(set.name)} (${sanitizeTerminalText(set.id)})`);
|
|
123
|
+
for (const partition of set.partitions)
|
|
124
|
+
printLine(` ${sanitizeTerminalText(partition.name)}: ${partition.row_count} rows`);
|
|
125
|
+
}
|
|
126
|
+
if (!data.split_sets.length)
|
|
127
|
+
printLine('No saved split sets.');
|
|
128
|
+
}
|
|
129
|
+
async function showDatasetSplitSet() {
|
|
130
|
+
const setId = getPositionalArg(3);
|
|
131
|
+
if (!setId)
|
|
132
|
+
throw new Error('Usage: orizu datasets splits show <splitSetId> [--json]');
|
|
133
|
+
const { showDatasetSplit } = await import('./dataset-split-reads.js');
|
|
134
|
+
const data = await showDatasetSplit(setId);
|
|
135
|
+
if (hasJsonFlag())
|
|
136
|
+
return printJson({ ...data });
|
|
137
|
+
printLine(`${sanitizeTerminalText(data.name)} (${sanitizeTerminalText(data.split_set_id)})`);
|
|
138
|
+
printLine(`Dataset version: ${sanitizeTerminalText(data.dataset_version_id)}`);
|
|
139
|
+
printLine(`Strategy: ${sanitizeTerminalText(data.strategy)}; seed: ${data.seed ?? 'none'}`);
|
|
140
|
+
printLine(`Metadata: ${sanitizeTerminalText(JSON.stringify(data.metadata))}`);
|
|
141
|
+
for (const partition of data.partitions) {
|
|
142
|
+
printLine(`${sanitizeTerminalText(partition.name)}: ${partition.row_ids.length} rows`);
|
|
143
|
+
for (const id of partition.row_ids)
|
|
144
|
+
printLine(` ${sanitizeTerminalText(id)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
119
147
|
async function createDatasetSplitSet() {
|
|
120
148
|
const datasetVersionId = getPositionalArg(3) || getArg('--dataset-version');
|
|
121
149
|
const splitFile = getArg('--from-file');
|
|
@@ -132,7 +160,7 @@ async function createDatasetSplitSet() {
|
|
|
132
160
|
throw new Error('Usage: orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--name <name>] [--seed <n>] [--train <ratio>] [--validation <ratio>] [--test <ratio>] [--json]');
|
|
133
161
|
}
|
|
134
162
|
const partitions = splitSpec && Array.isArray(splitSpec.partitions) ? splitSpec.partitions : undefined;
|
|
135
|
-
const metadata = splitSpec &&
|
|
163
|
+
const metadata = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'metadata')
|
|
136
164
|
? splitSpec.metadata
|
|
137
165
|
: undefined;
|
|
138
166
|
const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(datasetVersionId)}/split-sets`, {
|
|
@@ -264,25 +292,18 @@ async function setDatasetReadme() {
|
|
|
264
292
|
throw new Error(usage);
|
|
265
293
|
}
|
|
266
294
|
const resolvedDatasetId = await resolveDatasetIdForProject(datasetId, project);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
headers: { 'Content-Type': 'application/json' },
|
|
270
|
-
body: JSON.stringify({ markdown: readmeMarkdown }),
|
|
271
|
-
});
|
|
272
|
-
if (!response.ok) {
|
|
273
|
-
throw new Error(`Failed to save dataset README: ${await response.text()}`);
|
|
274
|
-
}
|
|
275
|
-
const data = await parseJsonResponse(response, 'Dataset README save');
|
|
295
|
+
const { saveDatasetReadme } = await import('./dataset-readme-workflow.js');
|
|
296
|
+
const version = await saveDatasetReadme(resolvedDatasetId, readmeMarkdown);
|
|
276
297
|
if (hasJsonFlag()) {
|
|
277
298
|
printJson({
|
|
278
|
-
dataset_id:
|
|
279
|
-
readme_version_id:
|
|
280
|
-
readme_version_num:
|
|
299
|
+
dataset_id: version.dataset_id,
|
|
300
|
+
readme_version_id: version.id,
|
|
301
|
+
readme_version_num: version.version_num,
|
|
281
302
|
});
|
|
282
303
|
return;
|
|
283
304
|
}
|
|
284
|
-
printLine(`Saved README v${
|
|
285
|
-
`${sanitizeTerminalText(
|
|
305
|
+
printLine(`Saved README v${version.version_num} for dataset ` +
|
|
306
|
+
`${sanitizeTerminalText(version.dataset_id)} (${sanitizeTerminalText(version.id)}).`);
|
|
286
307
|
}
|
|
287
308
|
function getDatasetReferenceInput() {
|
|
288
309
|
const fromFlag = getArg('--dataset');
|
|
@@ -303,6 +324,24 @@ async function downloadDataset() {
|
|
|
303
324
|
if (!['csv', 'json', 'jsonl'].includes(format)) {
|
|
304
325
|
throw new Error('format must be one of: csv, json, jsonl');
|
|
305
326
|
}
|
|
327
|
+
const versionId = getArg('--dataset-version'), setId = getArg('--split-set');
|
|
328
|
+
const inlineSplit = cliArgs.find(argument => argument.startsWith('--split='));
|
|
329
|
+
const split = inlineSplit === undefined ? getArg('--split') : inlineSplit.slice('--split='.length);
|
|
330
|
+
if (['--dataset-version', '--split-set', '--split'].some(flag => hasArg(flag) || cliArgs.some(argument => argument.startsWith(`${flag}=`)))) {
|
|
331
|
+
if (!versionId || !setId || !split || [versionId, setId].some(value => value.startsWith('--')) || (inlineSplit === undefined && split.startsWith('--')))
|
|
332
|
+
throw new Error('Pinned download requires --dataset-version, --split-set and --split together');
|
|
333
|
+
if (datasetInput)
|
|
334
|
+
throw new Error('Use --dataset-version with split selectors without a live --dataset or dataset argument');
|
|
335
|
+
if (format === 'csv')
|
|
336
|
+
throw new Error('Pinned download format must be json or jsonl');
|
|
337
|
+
const filename = outPathArg ? expandHomePath(outPathArg) : `${versionId}.${format}`;
|
|
338
|
+
const { downloadPinnedDatasetSplit } = await import('./dataset-split-reads.js');
|
|
339
|
+
await downloadPinnedDatasetSplit(versionId, setId, split, format, filename);
|
|
340
|
+
if (hasJsonFlag())
|
|
341
|
+
return printJson({ dataset_version_id: versionId, split_set_id: setId, split, format, savedTo: filename });
|
|
342
|
+
printLine(`Saved split ${sanitizeTerminalText(split)} from dataset version ${sanitizeTerminalText(versionId)} (${format.toUpperCase()}) to ${sanitizeTerminalText(filename)}`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
306
345
|
let datasetId;
|
|
307
346
|
if (datasetInput) {
|
|
308
347
|
datasetId = parseDatasetReference(datasetInput).datasetId;
|
|
@@ -349,7 +388,8 @@ async function editDatasetRows() {
|
|
|
349
388
|
const selected = await selectDatasetInteractively(projectArg);
|
|
350
389
|
datasetId = selected.datasetId;
|
|
351
390
|
}
|
|
352
|
-
await
|
|
391
|
+
const { editDatasetRows: runEditDatasetRows } = await import('./dataset-row-workflow.js');
|
|
392
|
+
const exitCode = await runEditDatasetRows({
|
|
353
393
|
datasetId,
|
|
354
394
|
file: expandHomePath(fileArg),
|
|
355
395
|
json: hasJsonFlag(),
|
|
@@ -357,6 +397,8 @@ async function editDatasetRows() {
|
|
|
357
397
|
printLine,
|
|
358
398
|
sanitize: sanitizeTerminalText,
|
|
359
399
|
});
|
|
400
|
+
if (exitCode !== 0)
|
|
401
|
+
process.exitCode = exitCode;
|
|
360
402
|
}
|
|
361
403
|
async function deleteDatasetRows() {
|
|
362
404
|
const projectArg = getArg('--project');
|
|
@@ -373,22 +415,17 @@ async function deleteDatasetRows() {
|
|
|
373
415
|
const selected = await selectDatasetInteractively(projectArg);
|
|
374
416
|
datasetId = selected.datasetId;
|
|
375
417
|
}
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
418
|
+
const { deleteDatasetRows: runDeleteDatasetRows } = await import('./dataset-row-workflow.js');
|
|
419
|
+
const exitCode = await runDeleteDatasetRows({
|
|
420
|
+
datasetId,
|
|
421
|
+
rowIds,
|
|
422
|
+
json: hasJsonFlag(),
|
|
423
|
+
printJson,
|
|
424
|
+
printLine,
|
|
425
|
+
sanitize: sanitizeTerminalText,
|
|
382
426
|
});
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
const data = await parseJsonResponse(response, 'Dataset delete rows');
|
|
387
|
-
if (hasJsonFlag()) {
|
|
388
|
-
printJson({ dataset: data.dataset, deletedCount: data.deletedCount });
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
printLine(`Deleted ${data.deletedCount} rows from dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`);
|
|
427
|
+
if (exitCode !== 0)
|
|
428
|
+
process.exitCode = exitCode;
|
|
392
429
|
}
|
|
393
430
|
async function confirmDatasetDeletion(dataset) {
|
|
394
431
|
if (!isInteractiveTerminal()) {
|
|
@@ -419,18 +456,15 @@ async function deleteDataset() {
|
|
|
419
456
|
dataset = await selectDatasetInteractively(projectArg);
|
|
420
457
|
}
|
|
421
458
|
await confirmDatasetDeletion(dataset);
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
throw new Error(`Delete failed: ${await response.text()}`);
|
|
427
|
-
}
|
|
428
|
-
const data = await parseJsonResponse(response, 'Dataset delete');
|
|
459
|
+
const { deleteDataset: requestDelete } = await import('./dataset-delete-workflow.js');
|
|
460
|
+
// Only a dataset picked from a project's list has a known project; with
|
|
461
|
+
// --dataset, --project is never checked against the dataset.
|
|
462
|
+
const deleted = await requestDelete(dataset.datasetId, dataset.project ?? null);
|
|
429
463
|
if (hasJsonFlag()) {
|
|
430
|
-
printJson({ dataset:
|
|
464
|
+
printJson({ dataset: deleted, deleted: true });
|
|
431
465
|
return;
|
|
432
466
|
}
|
|
433
|
-
printLine(`Deleted dataset ${sanitizeTerminalText(
|
|
467
|
+
printLine(`Deleted dataset ${sanitizeTerminalText(deleted.id)}.`);
|
|
434
468
|
}
|
|
435
469
|
async function lockDataset() {
|
|
436
470
|
const projectArg = getArg('--project');
|
|
@@ -444,20 +478,13 @@ async function lockDataset() {
|
|
|
444
478
|
const selected = await selectDatasetInteractively(projectArg);
|
|
445
479
|
datasetId = selected.datasetId;
|
|
446
480
|
}
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
headers: { 'Content-Type': 'application/json' },
|
|
450
|
-
body: JSON.stringify(reason ? { reason } : {}),
|
|
451
|
-
});
|
|
452
|
-
if (!response.ok) {
|
|
453
|
-
throw new Error(`Lock failed: ${await response.text()}`);
|
|
454
|
-
}
|
|
455
|
-
const data = await parseJsonResponse(response, 'Dataset lock');
|
|
481
|
+
const { lockDataset: requestLock } = await import('./dataset-lock-workflow.js');
|
|
482
|
+
const dataset = await requestLock(datasetId, reason);
|
|
456
483
|
if (hasJsonFlag()) {
|
|
457
|
-
printJson({ dataset
|
|
484
|
+
printJson({ dataset });
|
|
458
485
|
return;
|
|
459
486
|
}
|
|
460
|
-
printLine(`Locked dataset ${sanitizeTerminalText(
|
|
487
|
+
printLine(`Locked dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) at ${sanitizeTerminalText(dataset.lockedAt)}. Row count: ${dataset.rowCount}`);
|
|
461
488
|
}
|
|
462
489
|
async function cloneDataset() {
|
|
463
490
|
const projectArg = getArg('--project');
|
|
@@ -471,20 +498,13 @@ async function cloneDataset() {
|
|
|
471
498
|
const selected = await selectDatasetInteractively(projectArg);
|
|
472
499
|
datasetId = selected.datasetId;
|
|
473
500
|
}
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
headers: { 'Content-Type': 'application/json' },
|
|
477
|
-
body: JSON.stringify(name ? { name } : {}),
|
|
478
|
-
});
|
|
479
|
-
if (!response.ok) {
|
|
480
|
-
throw new Error(`Clone failed: ${await response.text()}`);
|
|
481
|
-
}
|
|
482
|
-
const data = await parseJsonResponse(response, 'Dataset clone');
|
|
501
|
+
const { cloneDataset: requestClone } = await import('./dataset-clone-workflow.js');
|
|
502
|
+
const dataset = await requestClone(datasetId, name ?? null);
|
|
483
503
|
if (hasJsonFlag()) {
|
|
484
|
-
printJson({ dataset
|
|
504
|
+
printJson({ dataset });
|
|
485
505
|
return;
|
|
486
506
|
}
|
|
487
|
-
printLine(`Cloned dataset ${sanitizeTerminalText(
|
|
507
|
+
printLine(`Cloned dataset ${sanitizeTerminalText(dataset.parentDatasetId)} -> ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}). Row count: ${dataset.rowCount}`);
|
|
488
508
|
}
|
|
489
509
|
async function downloadAnnotations() {
|
|
490
510
|
let taskId = getArg('--task');
|
|
@@ -496,20 +516,16 @@ async function downloadAnnotations() {
|
|
|
496
516
|
if (!taskId) {
|
|
497
517
|
taskId = await selectTaskIdInteractively();
|
|
498
518
|
}
|
|
499
|
-
const response = await authedFetch(`/api/cli/tasks/${taskId}/export?format=${format}`);
|
|
500
|
-
if (!response.ok) {
|
|
501
|
-
throw new Error(`Download failed: ${await response.text()}`);
|
|
502
|
-
}
|
|
503
519
|
const fallbackName = `${taskId}.${format}`;
|
|
504
520
|
const filename = outPathArg
|
|
505
521
|
? expandHomePath(outPathArg)
|
|
506
522
|
: fallbackName;
|
|
507
|
-
const
|
|
508
|
-
|
|
523
|
+
const { runTaskExportDownload } = await import('./task-export-workflow.js');
|
|
524
|
+
await runTaskExportDownload(taskId, format, filename);
|
|
509
525
|
if (hasJsonFlag()) {
|
|
510
526
|
printJson({ taskId, format, savedTo: filename });
|
|
511
527
|
return;
|
|
512
528
|
}
|
|
513
529
|
printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`);
|
|
514
530
|
}
|
|
515
|
-
export { uploadDataset, listDatasets, pushDataset, downloadDataset, appendDatasetRows, setDatasetReadme, createDatasetVersion, createDatasetSplitSet, editDatasetRows, deleteDatasetRows, deleteDataset, lockDataset, cloneDataset, downloadAnnotations, };
|
|
531
|
+
export { uploadDataset, listDatasets, pushDataset, downloadDataset, appendDatasetRows, setDatasetReadme, createDatasetVersion, createDatasetSplitSet, listDatasetSplitSets, showDatasetSplitSet, editDatasetRows, deleteDatasetRows, deleteDataset, lockDataset, cloneDataset, downloadAnnotations, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-delete-workflow.d.ts","sourceRoot":"","sources":["../src/dataset-delete-workflow.ts"],"names":[],"mappings":"AAmEA,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAItG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { authedFetch } from './http.js';
|
|
4
|
+
import { parseJsonResponse } from './json-response.js';
|
|
5
|
+
// Loaded only through `await import(...)` from dataset-commands, like append.
|
|
6
|
+
const MAY_BE_DELETED = 'The dataset may already be deleted: check with `orizu datasets list --status all` before retrying.';
|
|
7
|
+
const DeleteReply = Schema.Struct({ dataset: Schema.Struct({ id: Schema.String }) });
|
|
8
|
+
// A refusal (4xx) means nothing was deleted. After any other failure once the
|
|
9
|
+
// request was sent, the delete may have committed, and it cannot be undone.
|
|
10
|
+
class DeleteFailure extends Error {
|
|
11
|
+
mayBeDeleted;
|
|
12
|
+
constructor(message, mayBeDeleted) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.mayBeDeleted = mayBeDeleted;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function deleteRequest(datasetId, project, timeoutMs) {
|
|
18
|
+
return Effect.suspend(() => {
|
|
19
|
+
let sent = false;
|
|
20
|
+
let timedOut = false;
|
|
21
|
+
return Effect.tryPromise({
|
|
22
|
+
try: async (signal) => {
|
|
23
|
+
const request = new AbortController();
|
|
24
|
+
const abort = () => request.abort();
|
|
25
|
+
const timer = setTimeout(() => { timedOut = true; request.abort(); }, timeoutMs);
|
|
26
|
+
if (signal.aborted)
|
|
27
|
+
abort();
|
|
28
|
+
else
|
|
29
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
30
|
+
try {
|
|
31
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}`, {
|
|
32
|
+
method: 'DELETE',
|
|
33
|
+
signal: request.signal,
|
|
34
|
+
}, () => { sent = true; });
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const message = `Delete failed: ${await response.text()}`;
|
|
37
|
+
// A dependency refusal: archiving keeps the dataset and its references.
|
|
38
|
+
if (response.status === 409) {
|
|
39
|
+
const archive = `orizu datasets archive ${datasetId}${project ? ` --project ${project}` : ''}`;
|
|
40
|
+
throw new DeleteFailure(`${message}\nArchive it instead: \`${archive}\``, false);
|
|
41
|
+
}
|
|
42
|
+
throw new DeleteFailure(message, response.status < 400 || response.status >= 500);
|
|
43
|
+
}
|
|
44
|
+
const data = await parseJsonResponse(response, 'Dataset delete');
|
|
45
|
+
const decoded = Schema.decodeUnknownResult(DeleteReply)(data);
|
|
46
|
+
if (Result.isFailure(decoded)) {
|
|
47
|
+
throw new DeleteFailure(`Dataset delete returned an unexpected response (status ${response.status}).`, true);
|
|
48
|
+
}
|
|
49
|
+
return decoded.success.dataset;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
signal.removeEventListener('abort', abort);
|
|
54
|
+
request.abort();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
catch: cause => {
|
|
58
|
+
if (timedOut)
|
|
59
|
+
return new DeleteFailure('DATASET_TRANSFER_TIMEOUT: Dataset delete exceeded its deadline', true);
|
|
60
|
+
if (cause instanceof DeleteFailure)
|
|
61
|
+
return cause;
|
|
62
|
+
return new DeleteFailure(cause instanceof Error ? cause.message : String(cause), sent);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}).pipe(Effect.mapError(failure => new Error(failure.mayBeDeleted ? `${failure.message}\n${MAY_BE_DELETED}` : failure.message)));
|
|
66
|
+
}
|
|
67
|
+
// Runs after the typed confirmation. Nothing to clean up, so signals keep
|
|
68
|
+
// Node's default handling.
|
|
69
|
+
// `project` (team/project), when known, goes into the archive hint.
|
|
70
|
+
export async function deleteDataset(datasetId, project) {
|
|
71
|
+
const exit = await Effect.runPromiseExit(deleteRequest(datasetId, project, datasetTransferTimeoutMs()));
|
|
72
|
+
if (Exit.isSuccess(exit))
|
|
73
|
+
return exit.value;
|
|
74
|
+
throw Cause.squash(exit.cause);
|
|
75
|
+
}
|