primitive-admin 1.1.0-alpha.82 → 1.1.0-alpha.84
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/assets/skill/skills/primitive-platform/SKILL.md +105 -675
- package/dist/src/commands/collections.js +61 -0
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/databases.js +10 -2
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +38 -0
- package/dist/src/commands/documents.js +535 -62
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/functions.js +303 -121
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/locks.js +15 -1
- package/dist/src/commands/locks.js.map +1 -1
- package/dist/src/commands/sync.d.ts +50 -0
- package/dist/src/commands/sync.js +560 -312
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +103 -2
- package/dist/src/lib/api-client.js +162 -18
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.js +5 -5
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.js +8 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
- package/dist/src/lib/document-ingest-artifact.js +505 -0
- package/dist/src/lib/document-ingest-artifact.js.map +1 -0
- package/dist/src/lib/document-ingest-input.d.ts +51 -0
- package/dist/src/lib/document-ingest-input.js +132 -0
- package/dist/src/lib/document-ingest-input.js.map +1 -0
- package/dist/src/lib/document-ingest-rows.d.ts +137 -0
- package/dist/src/lib/document-ingest-rows.js +181 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -0
- package/dist/src/lib/document-ingest.d.ts +101 -0
- package/dist/src/lib/document-ingest.js +384 -0
- package/dist/src/lib/document-ingest.js.map +1 -0
- package/dist/src/lib/function-bundle.d.ts +6 -0
- package/dist/src/lib/function-bundle.js +8 -0
- package/dist/src/lib/function-bundle.js.map +1 -1
- package/dist/src/lib/function-collect.d.ts +1 -1
- package/dist/src/lib/function-collect.js +16 -0
- package/dist/src/lib/function-collect.js.map +1 -1
- package/dist/src/lib/function-db-types.d.ts +5 -1
- package/dist/src/lib/function-db-types.js +32 -3
- package/dist/src/lib/function-db-types.js.map +1 -1
- package/dist/src/lib/function-document-types.d.ts +83 -0
- package/dist/src/lib/function-document-types.js +155 -1
- package/dist/src/lib/function-document-types.js.map +1 -1
- package/dist/src/lib/function-grants-preflight.js +5 -0
- package/dist/src/lib/function-grants-preflight.js.map +1 -1
- package/dist/src/lib/function-log-lines.d.ts +76 -0
- package/dist/src/lib/function-log-lines.js +160 -0
- package/dist/src/lib/function-log-lines.js.map +1 -0
- package/dist/src/lib/function-log-row.d.ts +29 -0
- package/dist/src/lib/function-log-row.js +73 -0
- package/dist/src/lib/function-log-row.js.map +1 -0
- package/dist/src/lib/function-run.d.ts +53 -35
- package/dist/src/lib/function-run.js +55 -44
- package/dist/src/lib/function-run.js.map +1 -1
- package/dist/src/lib/function-schema-codegen.d.ts +6 -9
- package/dist/src/lib/function-schema-codegen.js +22 -35
- package/dist/src/lib/function-schema-codegen.js.map +1 -1
- package/dist/src/lib/function-sync.d.ts +226 -35
- package/dist/src/lib/function-sync.js +731 -99
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-versions.d.ts +122 -0
- package/dist/src/lib/function-versions.js +182 -0
- package/dist/src/lib/function-versions.js.map +1 -0
- package/dist/src/lib/generated-config-surfaces.d.ts +390 -146
- package/dist/src/lib/generated-config-surfaces.js +1207 -401
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/log-inspection.d.ts +2 -0
- package/dist/src/lib/log-inspection.js +4 -0
- package/dist/src/lib/log-inspection.js.map +1 -1
- package/dist/src/lib/output.d.ts +15 -0
- package/dist/src/lib/output.js +28 -0
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/resolve-owner.d.ts +19 -0
- package/dist/src/lib/resolve-owner.js +20 -0
- package/dist/src/lib/resolve-owner.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +49 -3
- package/dist/src/lib/skill-installer.js +275 -100
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
- package/dist/src/lib/snapshot-audit-source.js +58 -0
- package/dist/src/lib/snapshot-audit-source.js.map +1 -0
- package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
- package/dist/src/lib/snapshot-audit-store.js +196 -0
- package/dist/src/lib/snapshot-audit-store.js.map +1 -0
- package/dist/src/lib/snapshot-audit.d.ts +207 -0
- package/dist/src/lib/snapshot-audit.js +431 -0
- package/dist/src/lib/snapshot-audit.js.map +1 -0
- package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
- package/dist/src/lib/snapshot-build-rows.js +87 -0
- package/dist/src/lib/snapshot-build-rows.js.map +1 -0
- package/dist/src/lib/snapshot-build.d.ts +50 -0
- package/dist/src/lib/snapshot-build.js +111 -0
- package/dist/src/lib/snapshot-build.js.map +1 -0
- package/dist/src/lib/swift-codegen/functionGenerator.d.ts +23 -16
- package/dist/src/lib/swift-codegen/functionGenerator.js +34 -33
- package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +45 -2
- package/dist/src/lib/sync-selectors.js +66 -5
- package/dist/src/lib/sync-selectors.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,15 +1,38 @@
|
|
|
1
|
+
import { snapshotBuildRows } from "../lib/snapshot-build-rows.js";
|
|
2
|
+
import { driveSnapshotBuild } from "../lib/snapshot-build.js";
|
|
3
|
+
import { ingestClientRows, ingestSessionRows, } from "../lib/document-ingest-rows.js";
|
|
1
4
|
import { ApiClient } from "../lib/api-client.js";
|
|
2
5
|
import { resolveAppId } from "../lib/config.js";
|
|
3
|
-
import { success, error, info, formatTable, formatId, formatDate, json, keyValue, result as printResult, warn, } from "../lib/output.js";
|
|
6
|
+
import { success, error, info, formatTable, formatId, formatDate, json, keyValue, result as printResult, warn, flushOutput, } from "../lib/output.js";
|
|
4
7
|
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
8
|
+
import { resolveOwnerUserId } from "../lib/resolve-owner.js";
|
|
5
9
|
import { parseDataOption } from "../lib/data-input.js";
|
|
6
10
|
import { parseFilterOptions } from "../lib/record-filter.js";
|
|
7
11
|
import { buildPermissionsExport } from "../lib/document-export-permissions.js";
|
|
8
12
|
import { chunkAddressOf, renumberManifestForExport, } from "../lib/snapshot-manifest-layout.js";
|
|
9
13
|
import { validateSnapshotManifest } from "js-bao";
|
|
14
|
+
import { auditSnapshotChain, } from "../lib/snapshot-audit.js";
|
|
15
|
+
import { createAuditApiSource } from "../lib/snapshot-audit-source.js";
|
|
16
|
+
import { createAuditLocalStore, createChainFingerprintReader, } from "../lib/snapshot-audit-store.js";
|
|
17
|
+
import { discoverIngestInput, } from "../lib/document-ingest-input.js";
|
|
18
|
+
import { buildIngestArtifact, ingestSchemaFromIntrospection, } from "../lib/document-ingest-artifact.js";
|
|
19
|
+
import { driveDocumentIngest, summariseIngestPlan, assertIngestConfirmable, } from "../lib/document-ingest.js";
|
|
10
20
|
import { ulid } from "ulid";
|
|
11
21
|
import * as fs from "fs";
|
|
12
22
|
import * as path from "path";
|
|
23
|
+
/**
|
|
24
|
+
* One bulk-load session as rows (#3434, #3435, #3598).
|
|
25
|
+
*
|
|
26
|
+
* Shared by `documents ingests get` and by `documents ingest`'s own terminal
|
|
27
|
+
* report, so an operator reads the same thing however they arrived at it. The
|
|
28
|
+
* rows themselves are decided in `document-ingest-rows`, where they can be
|
|
29
|
+
* checked without a live server holding a session in the right state.
|
|
30
|
+
*/
|
|
31
|
+
function printIngestSession(session) {
|
|
32
|
+
for (const [label, value] of ingestSessionRows(session)) {
|
|
33
|
+
printResult(label, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
13
36
|
/**
|
|
14
37
|
* Report a document's format (#2816) — but only when it is a large document.
|
|
15
38
|
*
|
|
@@ -25,22 +48,85 @@ function printDocumentFormat(doc) {
|
|
|
25
48
|
}
|
|
26
49
|
}
|
|
27
50
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
51
|
+
* What `documents records bulk` reports when the write is refused (#3619).
|
|
52
|
+
*
|
|
53
|
+
* The command used to catch every failure with `error(err.message)` while
|
|
54
|
+
* `ApiError` carried the code and the status on separate fields, so the stable
|
|
55
|
+
* code the server answers never reached the output and `--json` handled
|
|
56
|
+
* successes only. An operator driving a multi-hour load had to read human text
|
|
57
|
+
* to tell "ask again" from "somebody look at the log" (finding 3619-SO-04).
|
|
58
|
+
*
|
|
59
|
+
* The status is read from `statusCode` FIRST: that is where
|
|
60
|
+
* `cli/src/lib/api-client.ts`'s `ApiError` keeps it, and a reader that asks for
|
|
61
|
+
* `status` alone sees `undefined` on every error the client raises and prints
|
|
62
|
+
* nothing at all — #3597 paid for that once already. `status` is the fallback,
|
|
63
|
+
* for an error that came from somewhere else.
|
|
64
|
+
*
|
|
65
|
+
* Its own function so both surfaces are composed once and can be graded
|
|
66
|
+
* without spawning a process: the human line and the `--json` envelope must
|
|
67
|
+
* agree about which failure happened.
|
|
34
68
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
69
|
+
export function describeBulkFailure(err) {
|
|
70
|
+
const message = (typeof err?.message === "string" && err.message) || String(err);
|
|
71
|
+
const code = typeof err?.code === "string" && err.code ? err.code : undefined;
|
|
72
|
+
const raw = err?.statusCode ?? err?.status;
|
|
73
|
+
const status = Number.isFinite(Number(raw)) ? Number(raw) : undefined;
|
|
74
|
+
return {
|
|
75
|
+
line: `${message}${code ? ` [${code}]` : ""}${status !== undefined ? ` (${status})` : ""}`,
|
|
76
|
+
envelope: {
|
|
77
|
+
ok: false,
|
|
78
|
+
...(code ? { code } : {}),
|
|
79
|
+
...(status !== undefined ? { status } : {}),
|
|
80
|
+
error: message,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A document's tags as a list, from whatever the wire handed back (#3644).
|
|
86
|
+
*
|
|
87
|
+
* `tags` is absent when the document has none — the server's presence rule,
|
|
88
|
+
* shared by the app API's list shape and the admin inventory — so every
|
|
89
|
+
* reader here has to cope with `undefined`. A stray non-array (an older
|
|
90
|
+
* server, a hand-edited fixture) is read as "no tags" rather than printed
|
|
91
|
+
* raw.
|
|
92
|
+
*/
|
|
93
|
+
function documentTagList(tags) {
|
|
94
|
+
return Array.isArray(tags)
|
|
95
|
+
? tags.filter((tag) => typeof tag === "string" && tag !== "")
|
|
96
|
+
: [];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Tag text a terminal prints rather than obeys (#3644).
|
|
100
|
+
*
|
|
101
|
+
* A tag is app-user text. The write-side validator
|
|
102
|
+
* (`validateTag` in `src/app-api/controllers/documents-controller.ts`) trims
|
|
103
|
+
* it, caps it at 48 characters and refuses dynamo-bao's key separator, but
|
|
104
|
+
* every other byte survives — including the escape character. Printed raw
|
|
105
|
+
* into an operator's terminal, a 15-character tag such as
|
|
106
|
+
* `\x1b[2J\x1b[Hrestored` clears the screen and homes the cursor while
|
|
107
|
+
* the listing is being written, so a member with write access to one document
|
|
108
|
+
* could hide or falsify what `documents list` and `documents get` appear to
|
|
109
|
+
* report about the rest. The C0 range, DEL and the C1 range are therefore
|
|
110
|
+
* rendered as their `\uXXXX` source escapes: readable, one line and one cell
|
|
111
|
+
* per tag, and inert. Ordinary text — accents, CJK, emoji — is untouched.
|
|
112
|
+
*/
|
|
113
|
+
function escapeForTerminal(tag) {
|
|
114
|
+
return tag.replace(/[\x00-\x1f\x7f-\x9f]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A document's tags as printable text, in the order the wire reported them.
|
|
118
|
+
*
|
|
119
|
+
* Only the plain-text surfaces come through here: `--json` on both commands
|
|
120
|
+
* returns the server's object untouched, so a migration diffing tags against
|
|
121
|
+
* what it wrote still reads the stored bytes.
|
|
122
|
+
*/
|
|
123
|
+
export function displayTags(tags) {
|
|
124
|
+
return documentTagList(tags).map(escapeForTerminal);
|
|
125
|
+
}
|
|
126
|
+
/** The tags cell of a documents table: comma-joined, or `-` when there are none. */
|
|
127
|
+
export function formatTags(tags) {
|
|
128
|
+
const list = displayTags(tags);
|
|
129
|
+
return list.length > 0 ? list.join(", ") : "-";
|
|
44
130
|
}
|
|
45
131
|
export function registerDocumentsCommands(program) {
|
|
46
132
|
const documents = program
|
|
@@ -95,6 +181,10 @@ Examples:
|
|
|
95
181
|
console.log(formatTable(list, [
|
|
96
182
|
{ header: "DOCUMENT_ID", key: "documentId", format: formatId },
|
|
97
183
|
{ header: "TITLE", key: "title", flex: true },
|
|
184
|
+
// #3644 — the tags the route now reports. An app migration that
|
|
185
|
+
// regenerates tags on import verifies them from here; a document
|
|
186
|
+
// with none shows `-` rather than an empty cell.
|
|
187
|
+
{ header: "TAGS", key: "tags", format: formatTags },
|
|
98
188
|
{ header: "PERMISSION", key: "permission" },
|
|
99
189
|
{ header: "GRANTED", key: "grantedAt", format: formatDate },
|
|
100
190
|
]));
|
|
@@ -126,6 +216,15 @@ Examples:
|
|
|
126
216
|
keyValue("Created", formatDate(doc.createdAt));
|
|
127
217
|
keyValue("Modified", formatDate(doc.modifiedAt ?? doc.lastModified));
|
|
128
218
|
printDocumentFormat(doc);
|
|
219
|
+
// #3644 — tags, when the document has any. The endpoint has returned
|
|
220
|
+
// them since #3096 but only `--json` showed them, so a migration
|
|
221
|
+
// verifying restored tags had to parse JSON to see its own work.
|
|
222
|
+
// Printed only when present, by the same rule the response uses: an
|
|
223
|
+
// untagged document's output is unchanged, and escaped the same way
|
|
224
|
+
// the table's cell is so a crafted tag cannot drive the terminal.
|
|
225
|
+
const tags = displayTags(doc.tags);
|
|
226
|
+
if (tags.length > 0)
|
|
227
|
+
keyValue("Tags", tags.join(", "));
|
|
129
228
|
// `GET documents/:id` reports the caller's own access, not the
|
|
130
229
|
// document's grant/alias/collection inventory — printing counts for
|
|
131
230
|
// fields the endpoint never returns read as a definitive "0" for
|
|
@@ -827,7 +926,11 @@ Permissions (enforced by the server, not the CLI):
|
|
|
827
926
|
`${result.added.length} added, ${result.updated.length} updated, ${result.deleted} deleted.`);
|
|
828
927
|
}
|
|
829
928
|
catch (err) {
|
|
830
|
-
|
|
929
|
+
const report = describeBulkFailure(err);
|
|
930
|
+
if (options.json)
|
|
931
|
+
json(report.envelope);
|
|
932
|
+
else
|
|
933
|
+
error(report.line);
|
|
831
934
|
process.exit(1);
|
|
832
935
|
}
|
|
833
936
|
});
|
|
@@ -900,6 +1003,214 @@ Permissions (enforced by the server, not the CLI):
|
|
|
900
1003
|
process.exit(1);
|
|
901
1004
|
}
|
|
902
1005
|
});
|
|
1006
|
+
// The action verb (#3435, criterion 9): a directory of records becomes a
|
|
1007
|
+
// chunked, digested, uploaded artifact and one atomic swap of the
|
|
1008
|
+
// document's tables. `documents ingests list|get` below is how an operator
|
|
1009
|
+
// watches one afterwards.
|
|
1010
|
+
//
|
|
1011
|
+
// Destructive by nature — a bulk load deletes and overwrites records in a
|
|
1012
|
+
// live document with no undo — so it confirms unless `-y`, which is what
|
|
1013
|
+
// `docs/cli-design.md` reserves the destructive default for.
|
|
1014
|
+
documents
|
|
1015
|
+
.command("ingest")
|
|
1016
|
+
.description("Bulk-load records into a large document from a directory")
|
|
1017
|
+
.argument("<document-id>", "Document ID")
|
|
1018
|
+
.requiredOption("--input <dir>", "Directory of <model>.ndjson[.gz] files, or a `documents export` output")
|
|
1019
|
+
.option("--app <app-id>", "App ID")
|
|
1020
|
+
.option("-y, --yes", "Skip the confirmation prompt")
|
|
1021
|
+
.option("--no-wait", "Exit once the session is committed")
|
|
1022
|
+
.option("--timeout <seconds>", "Give up waiting after this many seconds")
|
|
1023
|
+
.option("--json", "Output as JSON")
|
|
1024
|
+
.action(async (documentId, options) => {
|
|
1025
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
1026
|
+
const client = new ApiClient();
|
|
1027
|
+
let plan = null;
|
|
1028
|
+
// The exit code is carried out of here rather than taken inside: a
|
|
1029
|
+
// `process.exit` does not unwind, so ending the process in the try block
|
|
1030
|
+
// would skip the cleanup in the finally and leave the compressed
|
|
1031
|
+
// artifact — up to the whole input, gzipped — in the OS temp directory
|
|
1032
|
+
// on every run, successful or not (finding 3435-FROZEN-05).
|
|
1033
|
+
const run = async () => {
|
|
1034
|
+
// The preflight, in this order and before a byte is read from disk:
|
|
1035
|
+
// the schema read says whether this caller may see the document's
|
|
1036
|
+
// declarations at all and hands over the ones the lines are checked
|
|
1037
|
+
// against, and the session reader says whether this is a large
|
|
1038
|
+
// document. Both are READS on the arm the ingest routes admit, so a
|
|
1039
|
+
// caller who would be refused halfway is refused at once, and neither
|
|
1040
|
+
// opens a session.
|
|
1041
|
+
const introspection = await client.getDocumentSchema(resolvedAppId, documentId);
|
|
1042
|
+
await client.listDocumentIngests(resolvedAppId, documentId, { limit: 1 });
|
|
1043
|
+
const schema = ingestSchemaFromIntrospection(introspection);
|
|
1044
|
+
const input = discoverIngestInput(options.input, documentId);
|
|
1045
|
+
for (const ignored of input.ignored) {
|
|
1046
|
+
info(`Ignoring ${ignored}: a bulk load reads the snapshot only.`);
|
|
1047
|
+
}
|
|
1048
|
+
// #3598 — reading, checking, sorting, cutting and gzipping is this
|
|
1049
|
+
// side's biggest phase on a large input, and it used to be invisible
|
|
1050
|
+
// inside one wall-clock number that also covered the platform's work.
|
|
1051
|
+
const buildStartedAt = Date.now();
|
|
1052
|
+
plan = await buildIngestArtifact({ input, schema });
|
|
1053
|
+
const buildMs = Date.now() - buildStartedAt;
|
|
1054
|
+
info(`Built artifact in ${(buildMs / 1000).toFixed(1)}s.`);
|
|
1055
|
+
for (const line of summariseIngestPlan(plan))
|
|
1056
|
+
info(line);
|
|
1057
|
+
assertIngestConfirmable({
|
|
1058
|
+
yes: Boolean(options.yes),
|
|
1059
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
1060
|
+
});
|
|
1061
|
+
if (!options.yes) {
|
|
1062
|
+
const confirmed = await confirmPrompt(`Bulk-load ${plan.rows} records into ${documentId}? ` +
|
|
1063
|
+
"This replaces records in a live document and cannot be undone.");
|
|
1064
|
+
if (!confirmed) {
|
|
1065
|
+
info("Nothing was uploaded.");
|
|
1066
|
+
return 1;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// Ctrl-C is a decision the driver makes differently depending on
|
|
1070
|
+
// where it lands, so it is handed the signal rather than killing the
|
|
1071
|
+
// process: mid-upload the session is aborted, mid-poll it is left
|
|
1072
|
+
// running, because by then the document is already being changed.
|
|
1073
|
+
const interrupt = new AbortController();
|
|
1074
|
+
const onSignal = () => interrupt.abort();
|
|
1075
|
+
process.on("SIGINT", onSignal);
|
|
1076
|
+
let outcome;
|
|
1077
|
+
try {
|
|
1078
|
+
outcome = await driveDocumentIngest({
|
|
1079
|
+
client: {
|
|
1080
|
+
createIngest: (id) => client.createDocumentIngest(resolvedAppId, id),
|
|
1081
|
+
uploadIngestChunk: (id, sessionId, chunk) => client.uploadDocumentIngestChunk(resolvedAppId, id, sessionId, chunk),
|
|
1082
|
+
commitIngest: (id, sessionId) => client.commitDocumentIngest(resolvedAppId, id, sessionId),
|
|
1083
|
+
abortIngest: (id, sessionId) => client.abortDocumentIngest(resolvedAppId, id, sessionId),
|
|
1084
|
+
getIngest: (id, sessionId) => client.getDocumentIngest(resolvedAppId, id, sessionId),
|
|
1085
|
+
},
|
|
1086
|
+
documentId,
|
|
1087
|
+
plan,
|
|
1088
|
+
wait: options.wait !== false,
|
|
1089
|
+
timeoutMs: options.timeout ? Number(options.timeout) * 1000 : null,
|
|
1090
|
+
signal: interrupt.signal,
|
|
1091
|
+
onProgress: (line) => info(line),
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
finally {
|
|
1095
|
+
process.off("SIGINT", onSignal);
|
|
1096
|
+
}
|
|
1097
|
+
if (outcome.message)
|
|
1098
|
+
error(outcome.message);
|
|
1099
|
+
// #3598 — the two sides, side by side. The session's own `timings`
|
|
1100
|
+
// say where the platform's share went; `client` says what this
|
|
1101
|
+
// process spent before and around it, so a slow load can be blamed on
|
|
1102
|
+
// the side that was actually slow.
|
|
1103
|
+
const clientTimings = { buildMs, ...outcome.timings };
|
|
1104
|
+
if (options.json) {
|
|
1105
|
+
json({
|
|
1106
|
+
...(outcome.session ?? { sessionId: outcome.sessionId }),
|
|
1107
|
+
client: clientTimings,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
if (outcome.session) {
|
|
1112
|
+
printIngestSession(outcome.session);
|
|
1113
|
+
}
|
|
1114
|
+
else if (outcome.sessionId) {
|
|
1115
|
+
printResult("Session", outcome.sessionId);
|
|
1116
|
+
}
|
|
1117
|
+
for (const [label, value] of ingestClientRows(clientTimings)) {
|
|
1118
|
+
printResult(label, value);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return outcome.exitCode;
|
|
1122
|
+
};
|
|
1123
|
+
let code = 1;
|
|
1124
|
+
try {
|
|
1125
|
+
code = await run();
|
|
1126
|
+
}
|
|
1127
|
+
catch (err) {
|
|
1128
|
+
error(err.message);
|
|
1129
|
+
code = 1;
|
|
1130
|
+
}
|
|
1131
|
+
finally {
|
|
1132
|
+
await plan?.cleanup().catch(() => undefined);
|
|
1133
|
+
}
|
|
1134
|
+
// The result has to reach the pipe before the process ends: stdout to a
|
|
1135
|
+
// pipe is asynchronous and `process.exit` does not wait for it.
|
|
1136
|
+
await flushOutput();
|
|
1137
|
+
process.exit(code);
|
|
1138
|
+
});
|
|
1139
|
+
// Bulk-load sessions of a large document (#3434, criterion 9): a data noun
|
|
1140
|
+
// with `list` and `get` readers, per docs/cli-design.md, beside `snapshots`
|
|
1141
|
+
// below. The ACTION verb `documents ingest` is #3435's; these two are how an
|
|
1142
|
+
// operator sees what a session did and why one stopped.
|
|
1143
|
+
const ingests = documents
|
|
1144
|
+
.command("ingests")
|
|
1145
|
+
.description("Inspect a large document's bulk-load sessions");
|
|
1146
|
+
ingests
|
|
1147
|
+
.command("list")
|
|
1148
|
+
.description("List a large document's bulk-load sessions, newest first")
|
|
1149
|
+
.argument("<document-id>", "Document ID")
|
|
1150
|
+
.option("--app <app-id>", "App ID")
|
|
1151
|
+
.option("--limit <n>", "Page size (1–100)", "20")
|
|
1152
|
+
.option("--cursor <cursor>", "Continue from a previous page's nextCursor")
|
|
1153
|
+
.option("--json", "Output as JSON")
|
|
1154
|
+
.action(async (documentId, options) => {
|
|
1155
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
1156
|
+
const client = new ApiClient();
|
|
1157
|
+
try {
|
|
1158
|
+
const page = await client.listDocumentIngests(resolvedAppId, documentId, {
|
|
1159
|
+
limit: Number(options.limit),
|
|
1160
|
+
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
1161
|
+
});
|
|
1162
|
+
if (options.json) {
|
|
1163
|
+
json(page);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
if (page.items.length === 0) {
|
|
1167
|
+
info("No ingest sessions yet.");
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
console.log(formatTable(page.items, [
|
|
1171
|
+
{ header: "SESSION", key: "sessionId" },
|
|
1172
|
+
{ header: "STATE", key: "state" },
|
|
1173
|
+
{ header: "CREATED", key: "createdAt", format: formatDate },
|
|
1174
|
+
{ header: "CHUNKS", key: "chunks" },
|
|
1175
|
+
{ header: "ROWS", key: "rows" },
|
|
1176
|
+
{
|
|
1177
|
+
header: "COMPLETED",
|
|
1178
|
+
key: "completedAt",
|
|
1179
|
+
format: (at) => (at ? formatDate(at) : "—"),
|
|
1180
|
+
},
|
|
1181
|
+
]));
|
|
1182
|
+
if (page.hasMore && page.nextCursor) {
|
|
1183
|
+
info(`More sessions: --cursor ${page.nextCursor}`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
catch (err) {
|
|
1187
|
+
error(err.message);
|
|
1188
|
+
process.exit(1);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
ingests
|
|
1192
|
+
.command("get")
|
|
1193
|
+
.description("Show one bulk-load session, its progress and why it stopped")
|
|
1194
|
+
.argument("<document-id>", "Document ID")
|
|
1195
|
+
.argument("<session-id>", "Session ID")
|
|
1196
|
+
.option("--app <app-id>", "App ID")
|
|
1197
|
+
.option("--json", "Output as JSON")
|
|
1198
|
+
.action(async (documentId, sessionId, options) => {
|
|
1199
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
1200
|
+
const client = new ApiClient();
|
|
1201
|
+
try {
|
|
1202
|
+
const session = await client.getDocumentIngest(resolvedAppId, documentId, sessionId);
|
|
1203
|
+
if (options.json) {
|
|
1204
|
+
json(session);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
printIngestSession(session);
|
|
1208
|
+
}
|
|
1209
|
+
catch (err) {
|
|
1210
|
+
error(err.message);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
903
1214
|
// Snapshot builds of a large document (#3432, criterion 9): a data noun
|
|
904
1215
|
// with `list` and `get` readers, per docs/cli-design.md, over the app API's
|
|
905
1216
|
// read-only inspection routes.
|
|
@@ -974,47 +1285,11 @@ Permissions (enforced by the server, not the CLI):
|
|
|
974
1285
|
json(build);
|
|
975
1286
|
return;
|
|
976
1287
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
printResult("Started", formatDate(build.startedAt));
|
|
983
|
-
printResult("Completed", build.completedAt ? formatDate(build.completedAt) : "—");
|
|
984
|
-
printResult("Rows", build.rows);
|
|
985
|
-
printResult("Bytes", build.bytes);
|
|
986
|
-
printResult("Chunks", build.chunks);
|
|
987
|
-
const verification = build.verification;
|
|
988
|
-
printResult("Verification", verification ? verification.state : "not started");
|
|
989
|
-
if (verification) {
|
|
990
|
-
printResult("Checked at", formatDate(verification.checkedAt));
|
|
991
|
-
printResult("Chunks checked", verification.chunksChecked);
|
|
992
|
-
if (verification.code)
|
|
993
|
-
printResult("Code", verification.code);
|
|
994
|
-
if (verification.reason)
|
|
995
|
-
printResult("Reason", verification.reason);
|
|
996
|
-
}
|
|
997
|
-
if (build.failure) {
|
|
998
|
-
printResult("Failed at", formatDate(build.failure.at));
|
|
999
|
-
if (build.failure.chunk) {
|
|
1000
|
-
printResult("Failed on", `${build.failure.chunk.model} chunk ${build.failure.chunk.ordinal}`);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
const timings = build.timings ?? { ticks: 0, totals: {} };
|
|
1004
|
-
printResult("Ticks", timings.ticks);
|
|
1005
|
-
const stages = [
|
|
1006
|
-
["Read (ms)", "readMs"],
|
|
1007
|
-
["Encode (ms)", "encodeMs"],
|
|
1008
|
-
["Compress (ms)", "compressMs"],
|
|
1009
|
-
["Hash (ms)", "hashMs"],
|
|
1010
|
-
["Upload (ms)", "uploadMs"],
|
|
1011
|
-
["Manifest (ms)", "manifestMs"],
|
|
1012
|
-
["Verify read (ms)", "verifyReadMs"],
|
|
1013
|
-
["Verify digest (ms)", "verifyDigestMs"],
|
|
1014
|
-
["Verify coverage (ms)", "verifyCoverageMs"],
|
|
1015
|
-
];
|
|
1016
|
-
for (const [label, key] of stages) {
|
|
1017
|
-
printResult(label, timings.totals?.[key] ?? 0);
|
|
1288
|
+
// The rows are composed by a pure function (#3435), so what a
|
|
1289
|
+
// terminal shows is checkable without spawning one; the command still
|
|
1290
|
+
// owns the printing.
|
|
1291
|
+
for (const [label, value] of snapshotBuildRows(build)) {
|
|
1292
|
+
printResult(label, value);
|
|
1018
1293
|
}
|
|
1019
1294
|
}
|
|
1020
1295
|
catch (err) {
|
|
@@ -1022,13 +1297,178 @@ Permissions (enforced by the server, not the CLI):
|
|
|
1022
1297
|
process.exit(1);
|
|
1023
1298
|
}
|
|
1024
1299
|
});
|
|
1300
|
+
// #3666, criterion 7 — the one ACTION verb in this group: snapshot the
|
|
1301
|
+
// document now instead of waiting for the size or age trigger. Its exit
|
|
1302
|
+
// codes are `documents ingest`'s, as `docs/cli-design.md` records them, and
|
|
1303
|
+
// for the same reason: giving up on WATCHING a build is not the build
|
|
1304
|
+
// failing.
|
|
1305
|
+
//
|
|
1306
|
+
// Not destructive, so it does not confirm: a seal rotates the overlay the
|
|
1307
|
+
// room is writing to and loses nothing, which is what the size trigger does
|
|
1308
|
+
// on its own several times a day.
|
|
1309
|
+
snapshots
|
|
1310
|
+
.command("build")
|
|
1311
|
+
.description("Snapshot a large document now: seal the open epoch and build a base")
|
|
1312
|
+
.argument("<document-id>", "Document ID")
|
|
1313
|
+
.option("--app <app-id>", "App ID")
|
|
1314
|
+
.option("--wait", "Poll until the build has verified or failed")
|
|
1315
|
+
.option("--timeout <seconds>", "Give up waiting after this many seconds")
|
|
1316
|
+
.option("--json", "Output as JSON")
|
|
1317
|
+
.action(async (documentId, options) => {
|
|
1318
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
1319
|
+
const client = new ApiClient();
|
|
1320
|
+
const run = async () => {
|
|
1321
|
+
// Ctrl-C while polling leaves the build running — by then the room is
|
|
1322
|
+
// already building it and giving up on watching is not giving up on
|
|
1323
|
+
// the build — so the signal is handed to the driver rather than
|
|
1324
|
+
// killing the process.
|
|
1325
|
+
const interrupt = new AbortController();
|
|
1326
|
+
const onSignal = () => interrupt.abort();
|
|
1327
|
+
process.on("SIGINT", onSignal);
|
|
1328
|
+
let outcome;
|
|
1329
|
+
try {
|
|
1330
|
+
outcome = await driveSnapshotBuild({
|
|
1331
|
+
client: {
|
|
1332
|
+
requestSnapshot: (id) => client.requestDocumentSnapshot(resolvedAppId, id),
|
|
1333
|
+
getSnapshot: (id, buildId) => client.getDocumentSnapshot(resolvedAppId, id, buildId),
|
|
1334
|
+
},
|
|
1335
|
+
documentId,
|
|
1336
|
+
wait: Boolean(options.wait),
|
|
1337
|
+
timeoutMs: options.timeout ? Number(options.timeout) * 1000 : null,
|
|
1338
|
+
signal: interrupt.signal,
|
|
1339
|
+
onProgress: (line) => info(line),
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
finally {
|
|
1343
|
+
process.off("SIGINT", onSignal);
|
|
1344
|
+
}
|
|
1345
|
+
if (outcome.message)
|
|
1346
|
+
error(outcome.message);
|
|
1347
|
+
if (options.json) {
|
|
1348
|
+
// The route's answer as received, with the build the wait settled
|
|
1349
|
+
// on beside it when there was one — so a script branches on the
|
|
1350
|
+
// verdict rather than on an exit code alone.
|
|
1351
|
+
json(outcome.build ? { ...outcome.response, build: outcome.build } : outcome.response);
|
|
1352
|
+
return outcome.exitCode;
|
|
1353
|
+
}
|
|
1354
|
+
if (outcome.response?.sealed === false) {
|
|
1355
|
+
printResult("Sealed", "no — the open epoch carried nothing");
|
|
1356
|
+
printResult("Reason", outcome.response.reason);
|
|
1357
|
+
printResult("Covering build", outcome.response.coveringBuildId ?? "—");
|
|
1358
|
+
return outcome.exitCode;
|
|
1359
|
+
}
|
|
1360
|
+
printResult("Sealed epoch", outcome.response.sealedEpoch);
|
|
1361
|
+
printResult("Open epoch", outcome.response.nextEpoch);
|
|
1362
|
+
printResult("Build", outcome.response.buildId);
|
|
1363
|
+
if (outcome.build) {
|
|
1364
|
+
for (const [label, value] of snapshotBuildRows(outcome.build)) {
|
|
1365
|
+
printResult(label, value);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return outcome.exitCode;
|
|
1369
|
+
};
|
|
1370
|
+
let code = 1;
|
|
1371
|
+
try {
|
|
1372
|
+
code = await run();
|
|
1373
|
+
}
|
|
1374
|
+
catch (err) {
|
|
1375
|
+
// The server's own sentence, plus the code a script branches on
|
|
1376
|
+
// (#3403) — a refused loop that only says "too young to seal" leaves
|
|
1377
|
+
// a caller parsing prose to tell it from a permission refusal.
|
|
1378
|
+
error(err.code ? `${err.message} (${err.code})` : err.message);
|
|
1379
|
+
const retryAfterMs = err.details?.retryAfterMs;
|
|
1380
|
+
if (typeof retryAfterMs === "number") {
|
|
1381
|
+
error(`Ask again in ${Math.ceil(retryAfterMs / 1000)}s.`);
|
|
1382
|
+
}
|
|
1383
|
+
code = 1;
|
|
1384
|
+
}
|
|
1385
|
+
// The result has to reach the pipe before the process ends: stdout to a
|
|
1386
|
+
// pipe is asynchronous and `process.exit` does not wait for it.
|
|
1387
|
+
await flushOutput();
|
|
1388
|
+
process.exit(code);
|
|
1389
|
+
});
|
|
1390
|
+
// #3433, criterion 10 — the operator audit: `snapshot ⊕ overlays == table`,
|
|
1391
|
+
// recomputed on this side with the CLIENT's loader and fold and compared
|
|
1392
|
+
// with the authoritative table page by page. The one check that can catch a
|
|
1393
|
+
// DO/client fold divergence, which per-build verification runs on one side
|
|
1394
|
+
// of and therefore cannot see.
|
|
1395
|
+
snapshots
|
|
1396
|
+
.command("audit")
|
|
1397
|
+
.description("Recompute a large document's snapshot chain locally and compare it with the table")
|
|
1398
|
+
.argument("<document-id>", "Document ID")
|
|
1399
|
+
.option("--app <app-id>", "App ID")
|
|
1400
|
+
.option("--retries <n>", "Attempts before giving up on a document that keeps changing", "3")
|
|
1401
|
+
.option("--json", "Output as JSON")
|
|
1402
|
+
.action(async (documentId, options) => {
|
|
1403
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
1404
|
+
const client = new ApiClient();
|
|
1405
|
+
const report = await auditDocumentChain(client, resolvedAppId, documentId, Number(options.retries));
|
|
1406
|
+
if (options.json) {
|
|
1407
|
+
json(report);
|
|
1408
|
+
}
|
|
1409
|
+
else if (report.verdict === "none") {
|
|
1410
|
+
// No verdict is not a clean bill of health, so nothing that could be
|
|
1411
|
+
// read as one is printed: the reason, and nothing else.
|
|
1412
|
+
error(`No verdict: ${report.reason}`);
|
|
1413
|
+
}
|
|
1414
|
+
else {
|
|
1415
|
+
printResult("Document", documentId);
|
|
1416
|
+
printResult("Base build", report.buildId);
|
|
1417
|
+
printResult("Base epoch", report.epoch);
|
|
1418
|
+
printResult("Attempts", report.attempts);
|
|
1419
|
+
printResult("Verdict", report.verdict === "match"
|
|
1420
|
+
? "match — the table is the chain"
|
|
1421
|
+
: "DIVERGENCE");
|
|
1422
|
+
console.log(formatTable(report.models, [
|
|
1423
|
+
{ header: "MODEL", key: "model" },
|
|
1424
|
+
{ header: "COMPARED", key: "compared" },
|
|
1425
|
+
{
|
|
1426
|
+
header: "ONLY IN CHAIN",
|
|
1427
|
+
key: "onlyRecomputed",
|
|
1428
|
+
format: (ids) => (ids.length ? ids.join(", ") : "-"),
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
header: "ONLY IN TABLE",
|
|
1432
|
+
key: "onlyTable",
|
|
1433
|
+
format: (ids) => (ids.length ? ids.join(", ") : "-"),
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
header: "DIFFERING",
|
|
1437
|
+
key: "differing",
|
|
1438
|
+
format: (rows) => rows.length
|
|
1439
|
+
? rows.map((row) => `${row.id} (${row.fields.join(", ")})`).join("; ")
|
|
1440
|
+
: "-",
|
|
1441
|
+
},
|
|
1442
|
+
]));
|
|
1443
|
+
if (report.models.some((model) => model.truncated)) {
|
|
1444
|
+
info("Some lists were truncated; re-run with --json for the full report.");
|
|
1445
|
+
}
|
|
1446
|
+
if (report.verdict === "match")
|
|
1447
|
+
success("snapshot ⊕ overlays == table");
|
|
1448
|
+
}
|
|
1449
|
+
// The report has to reach the pipe before the process ends: stdout to a
|
|
1450
|
+
// pipe is asynchronous and `process.exit` does not wait for it, so a
|
|
1451
|
+
// large report piped into anything that is not reading as fast as this
|
|
1452
|
+
// writes used to arrive cut off mid-document (3433-C12).
|
|
1453
|
+
await flushOutput();
|
|
1454
|
+
// 0 match, 1 divergence, 2 no verdict — so the validation child can
|
|
1455
|
+
// script on "no verdict" without reading the text.
|
|
1456
|
+
process.exit(report.verdict === "match" ? 0 : report.verdict === "divergence" ? 1 : 2);
|
|
1457
|
+
});
|
|
1025
1458
|
// Transfer document ownership
|
|
1026
1459
|
documents
|
|
1027
1460
|
.command("transfer-owner")
|
|
1028
1461
|
.description("Transfer document ownership to another user")
|
|
1462
|
+
// Every positional is declared optional and the action decides which is
|
|
1463
|
+
// which (#3645). A required positional AFTER an optional one makes the
|
|
1464
|
+
// optional one mandatory: commander binds the first value it sees to
|
|
1465
|
+
// `[app-id]` and then reports the required argument missing, so
|
|
1466
|
+
// `transfer-owner <document-id> <new-owner-id>` never reached the action.
|
|
1467
|
+
// What each slot actually requires is enforced below, where the shift is
|
|
1468
|
+
// already known.
|
|
1029
1469
|
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
1030
|
-
.argument("
|
|
1031
|
-
.argument("
|
|
1470
|
+
.argument("[document-id]", "Document ID (required)")
|
|
1471
|
+
.argument("[new-owner-id]", "User ID of the new owner (required)")
|
|
1032
1472
|
.option("--app <app-id>", "App ID")
|
|
1033
1473
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
1034
1474
|
.option("--json", "Output as JSON")
|
|
@@ -1230,8 +1670,11 @@ Permissions (enforced by the server, not the CLI):
|
|
|
1230
1670
|
documents
|
|
1231
1671
|
.command("export")
|
|
1232
1672
|
.description("Export a document (Yjs state, blobs, permissions, aliases)")
|
|
1673
|
+
// Optional in commander, required in the action (#3645) — see
|
|
1674
|
+
// `transfer-owner` above for why a `<...>` here would make `[app-id]`
|
|
1675
|
+
// mandatory.
|
|
1233
1676
|
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
1234
|
-
.argument("
|
|
1677
|
+
.argument("[document-id]", "Document ID to export (required)")
|
|
1235
1678
|
.option("--app <app-id>", "App ID")
|
|
1236
1679
|
.option("--output <dir>", "Output directory", "./primitive-export")
|
|
1237
1680
|
.option("--no-blobs", "Skip blob data")
|
|
@@ -1342,8 +1785,9 @@ Permissions (enforced by the server, not the CLI):
|
|
|
1342
1785
|
documents
|
|
1343
1786
|
.command("import")
|
|
1344
1787
|
.description("Import documents from an export directory")
|
|
1788
|
+
// Optional in commander, required in the action (#3645).
|
|
1345
1789
|
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
1346
|
-
.argument("
|
|
1790
|
+
.argument("[path]", "Path to export directory or single document directory (required)")
|
|
1347
1791
|
.option("--app <app-id>", "App ID")
|
|
1348
1792
|
.option("--aliases <mode>", "Alias conflict handling: overwrite or skip", "skip")
|
|
1349
1793
|
// `--overwrite` has never replaced a document: the import path issues no
|
|
@@ -2360,4 +2804,33 @@ async function importRootDocument(client, appId, docDir, metadata, plan, overwri
|
|
|
2360
2804
|
`root document ${targetRootDocId}`);
|
|
2361
2805
|
}
|
|
2362
2806
|
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Wire the audit to the app API (#3433, criterion 10).
|
|
2809
|
+
*
|
|
2810
|
+
* Two permissions, not one (3433-C08): the chain export takes the app-admin
|
|
2811
|
+
* arm, and the schema and records reads it is compared against take the
|
|
2812
|
+
* console arm or a read grant on the document — the documents surface grants
|
|
2813
|
+
* no app-role bypass on a read (#3279, D3279-002). The schema is read first,
|
|
2814
|
+
* so a caller holding only the first is refused before anything is exported.
|
|
2815
|
+
*/
|
|
2816
|
+
async function auditDocumentChain(client, appId, documentId, retries) {
|
|
2817
|
+
// One page's worth of records is the audit's whole working set on the
|
|
2818
|
+
// server side; everything else lives in the local store.
|
|
2819
|
+
const AUDIT_PAGE_SIZE = 200;
|
|
2820
|
+
const fingerprint = await createChainFingerprintReader();
|
|
2821
|
+
// Nothing the server answers is carried from one attempt to the next: the
|
|
2822
|
+
// attempt a moved document discards is exactly the one whose schema has
|
|
2823
|
+
// changed under it (3433-C09).
|
|
2824
|
+
const source = createAuditApiSource(client, {
|
|
2825
|
+
appId,
|
|
2826
|
+
documentId,
|
|
2827
|
+
pageSize: AUDIT_PAGE_SIZE,
|
|
2828
|
+
});
|
|
2829
|
+
return await auditSnapshotChain(source, {
|
|
2830
|
+
store: () => createAuditLocalStore(documentId),
|
|
2831
|
+
fingerprint,
|
|
2832
|
+
retries,
|
|
2833
|
+
pageSize: AUDIT_PAGE_SIZE,
|
|
2834
|
+
});
|
|
2835
|
+
}
|
|
2363
2836
|
//# sourceMappingURL=documents.js.map
|