@superdoc/sdk 2.9.1-next.2 → 2.10.0-next.10
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 +45 -0
- package/dist/action-primitives/doc-index.cjs +6 -4
- package/dist/action-primitives/doc-index.js +6 -4
- package/dist/action-primitives/engine.cjs +6 -2
- package/dist/action-primitives/engine.js +6 -2
- package/dist/action-primitives/receipt.d.ts +4 -0
- package/dist/action-primitives/tools/structure-insert.d.ts +1 -1
- package/dist/agent/actions.cjs +384 -373
- package/dist/agent/actions.d.ts +5 -0
- package/dist/agent/actions.js +385 -374
- package/dist/agent/catalog.cjs +15 -0
- package/dist/agent/catalog.js +15 -0
- package/dist/agent/doc-snapshot.cjs +205 -86
- package/dist/agent/doc-snapshot.d.ts +11 -0
- package/dist/agent/doc-snapshot.js +203 -86
- package/dist/agent/execution-context.cjs +385 -0
- package/dist/agent/execution-context.d.ts +97 -0
- package/dist/agent/execution-context.js +376 -0
- package/dist/agent/runtime.cjs +123 -117
- package/dist/agent/runtime.d.ts +3 -0
- package/dist/agent/runtime.js +124 -118
- package/dist/agent/v2-preset-compat.cjs +5 -1
- package/dist/agent/v2-preset-compat.js +4 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.cjs +2 -0
- package/dist/generated/client.d.ts +85 -0
- package/dist/generated/client.js +2 -0
- package/dist/generated/contract.cjs +1796 -1297
- package/dist/generated/contract.js +1797 -1297
- package/dist/index.cjs +23 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +23 -0
- package/dist/presets/core.cjs +1 -1
- package/dist/presets/core.js +1 -1
- package/dist/runtime/document-evidence.cjs +40 -0
- package/dist/runtime/document-evidence.d.ts +13 -0
- package/dist/runtime/document-evidence.js +30 -0
- package/dist/runtime/document-rpc.cjs +27 -0
- package/dist/runtime/document-rpc.d.ts +2 -0
- package/dist/runtime/document-rpc.js +25 -0
- package/dist/runtime/host.cjs +65 -4
- package/dist/runtime/host.d.ts +3 -0
- package/dist/runtime/host.js +66 -5
- package/dist/runtime/process.cjs +38 -0
- package/dist/runtime/process.d.ts +16 -0
- package/dist/runtime/process.js +38 -0
- package/dist/runtime/sdk-version.generated.cjs +1 -1
- package/dist/runtime/sdk-version.generated.d.ts +1 -1
- package/dist/runtime/sdk-version.generated.js +1 -1
- package/package.json +10 -8
- package/tools/catalog.json +89 -0
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +89 -0
- package/tools/tools.generic.json +89 -0
- package/tools/tools.openai.json +89 -0
- package/tools/tools.vercel.json +89 -0
package/dist/agent/catalog.cjs
CHANGED
|
@@ -146,6 +146,11 @@ const ACTION_ARG_SCHEMA = {
|
|
|
146
146
|
},
|
|
147
147
|
finds: { type: 'array', items: { type: 'string' } },
|
|
148
148
|
caseSensitive: { type: 'boolean' },
|
|
149
|
+
evidence: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
enum: ['required', 'full'],
|
|
152
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
153
|
+
},
|
|
149
154
|
changeMode: { type: 'string', enum: ['direct', 'tracked'] },
|
|
150
155
|
placement: { type: 'object', additionalProperties: true, properties: {} },
|
|
151
156
|
selector: SELECTOR_SCHEMA,
|
|
@@ -390,6 +395,11 @@ const AGENT_TOOL_DEFINITIONS = [
|
|
|
390
395
|
additionalProperties: false,
|
|
391
396
|
required: ['plan'],
|
|
392
397
|
properties: {
|
|
398
|
+
evidence: {
|
|
399
|
+
type: 'string',
|
|
400
|
+
enum: ['required', 'full'],
|
|
401
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
402
|
+
},
|
|
393
403
|
plan: {
|
|
394
404
|
type: 'object',
|
|
395
405
|
additionalProperties: false,
|
|
@@ -428,6 +438,11 @@ const AGENT_TOOL_DEFINITIONS = [
|
|
|
428
438
|
additionalProperties: false,
|
|
429
439
|
required: ['checks'],
|
|
430
440
|
properties: {
|
|
441
|
+
evidence: {
|
|
442
|
+
type: 'string',
|
|
443
|
+
enum: ['required', 'full'],
|
|
444
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
445
|
+
},
|
|
431
446
|
checks: {
|
|
432
447
|
type: 'array',
|
|
433
448
|
minItems: 1,
|
package/dist/agent/catalog.js
CHANGED
|
@@ -143,6 +143,11 @@ export const ACTION_ARG_SCHEMA = {
|
|
|
143
143
|
},
|
|
144
144
|
finds: { type: 'array', items: { type: 'string' } },
|
|
145
145
|
caseSensitive: { type: 'boolean' },
|
|
146
|
+
evidence: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
enum: ['required', 'full'],
|
|
149
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
150
|
+
},
|
|
146
151
|
changeMode: { type: 'string', enum: ['direct', 'tracked'] },
|
|
147
152
|
placement: { type: 'object', additionalProperties: true, properties: {} },
|
|
148
153
|
selector: SELECTOR_SCHEMA,
|
|
@@ -387,6 +392,11 @@ export const AGENT_TOOL_DEFINITIONS = [
|
|
|
387
392
|
additionalProperties: false,
|
|
388
393
|
required: ['plan'],
|
|
389
394
|
properties: {
|
|
395
|
+
evidence: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
enum: ['required', 'full'],
|
|
398
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
399
|
+
},
|
|
390
400
|
plan: {
|
|
391
401
|
type: 'object',
|
|
392
402
|
additionalProperties: false,
|
|
@@ -425,6 +435,11 @@ export const AGENT_TOOL_DEFINITIONS = [
|
|
|
425
435
|
additionalProperties: false,
|
|
426
436
|
required: ['checks'],
|
|
427
437
|
properties: {
|
|
438
|
+
evidence: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
enum: ['required', 'full'],
|
|
441
|
+
description: 'Required evidence by default; full includes complete count summaries.',
|
|
442
|
+
},
|
|
428
443
|
checks: {
|
|
429
444
|
type: 'array',
|
|
430
445
|
minItems: 1,
|
|
@@ -99,6 +99,48 @@ function includesDomain(requested, domain) {
|
|
|
99
99
|
function truncateBlockText(value, limit) {
|
|
100
100
|
return limit == null ? value : value.slice(0, limit);
|
|
101
101
|
}
|
|
102
|
+
function populateTableCellsFromExtract(tables, extractRaw, tableOrdinalBase = 0) {
|
|
103
|
+
const extractRec = asRecord(extractRaw);
|
|
104
|
+
const extractBlocks = Array.isArray(extractRec?.blocks) ? extractRec.blocks : [];
|
|
105
|
+
const cellsByTableNodeId = new Map();
|
|
106
|
+
for (const block of extractBlocks) {
|
|
107
|
+
const rec = asRecord(block);
|
|
108
|
+
const tableContext = asRecord(rec?.tableContext);
|
|
109
|
+
if (!rec || !tableContext)
|
|
110
|
+
continue;
|
|
111
|
+
const tableOrdinal = asNumber(tableContext.tableOrdinal, -1);
|
|
112
|
+
const rowIndex = asNumber(tableContext.rowIndex, -1);
|
|
113
|
+
const columnIndex = asNumber(tableContext.columnIndex, -1);
|
|
114
|
+
if (tableOrdinal < 0 || rowIndex < 0 || columnIndex < 0)
|
|
115
|
+
continue;
|
|
116
|
+
const table = tables[tableOrdinal - tableOrdinalBase];
|
|
117
|
+
if (!table)
|
|
118
|
+
continue;
|
|
119
|
+
const key = `${rowIndex}:${columnIndex}`;
|
|
120
|
+
const text = asString(rec.text);
|
|
121
|
+
const nodeId = asString(rec.nodeId) || undefined;
|
|
122
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId) ??
|
|
123
|
+
new Map();
|
|
124
|
+
const existing = cellMap.get(key);
|
|
125
|
+
cellMap.set(key, {
|
|
126
|
+
rowIndex,
|
|
127
|
+
columnIndex,
|
|
128
|
+
text: existing == null || text.length === 0
|
|
129
|
+
? (existing?.text ?? text)
|
|
130
|
+
: existing.text.length === 0
|
|
131
|
+
? text
|
|
132
|
+
: `${existing.text}\n${text}`,
|
|
133
|
+
nodeId: existing?.nodeId ?? nodeId,
|
|
134
|
+
});
|
|
135
|
+
cellsByTableNodeId.set(table.nodeId, cellMap);
|
|
136
|
+
}
|
|
137
|
+
for (const table of tables) {
|
|
138
|
+
const cellMap = cellsByTableNodeId.get(table.nodeId);
|
|
139
|
+
if (!cellMap)
|
|
140
|
+
continue;
|
|
141
|
+
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
102
144
|
/**
|
|
103
145
|
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
104
146
|
* read-mode operations from the generated contract — it never mutates state,
|
|
@@ -117,57 +159,33 @@ async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
117
159
|
// first match so one call both LOCATES and READS the target section.
|
|
118
160
|
let finds;
|
|
119
161
|
if (typeof options.findText === 'string' && options.findText.trim().length > 0) {
|
|
120
|
-
const scanFn = maybeMethod(doc, ['blocks', '
|
|
162
|
+
const scanFn = maybeMethod(doc, ['blocks', 'findText']);
|
|
121
163
|
if (scanFn) {
|
|
122
|
-
// Paginated scan with a hard cap: bounded bridge payloads per page, and
|
|
123
|
-
// an explicit truncated flag instead of a silently-partial `total`.
|
|
124
|
-
const SCAN_PAGE = 2000;
|
|
125
|
-
const SCAN_CAP = 20000;
|
|
126
|
-
const needle = options.findText.toLowerCase();
|
|
127
164
|
const findLimit = options.findLimit != null && options.findLimit > 0 ? options.findLimit : 8;
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const scanBlocks = Array.isArray(scanRec?.blocks) ? scanRec.blocks : [];
|
|
135
|
-
scanBlocks.forEach((b, i) => {
|
|
136
|
-
const rec = asRecord(b) ?? {};
|
|
137
|
-
const text = asString(rec.text);
|
|
138
|
-
if (!text || !text.toLowerCase().includes(needle))
|
|
139
|
-
return;
|
|
140
|
-
matches.push({
|
|
141
|
-
ordinal: asNumber(rec.ordinal, pageOffset + i) + 1,
|
|
142
|
-
nodeId: asString(rec.nodeId),
|
|
143
|
-
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
144
|
-
preview: text.slice(0, 100),
|
|
145
|
-
index: asNumber(rec.ordinal, pageOffset + i),
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
scanned += scanBlocks.length;
|
|
149
|
-
const totalBlocksReported = explicitCount(scanRec ?? {}, 'total');
|
|
150
|
-
if (scanBlocks.length < SCAN_PAGE)
|
|
151
|
-
break; // last page
|
|
152
|
-
if (totalBlocksReported != null && scanned >= totalBlocksReported)
|
|
153
|
-
break;
|
|
154
|
-
if (pageOffset + SCAN_PAGE >= SCAN_CAP) {
|
|
155
|
-
truncated = true;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
165
|
+
const scanRaw = await safeCall(() => scanFn({ text: options.findText, limit: Math.min(20000, Math.trunc(findLimit)) }), null, recordError('blocks.findText'));
|
|
166
|
+
const scan = asRecord(scanRaw);
|
|
167
|
+
const scanError = asRecord(scan?.scanError);
|
|
168
|
+
if (typeof scanError?.message === 'string')
|
|
169
|
+
recordError('blocks.findText')(scanError.message);
|
|
170
|
+
const matches = Array.isArray(scan?.matches) ? scan.matches : [];
|
|
159
171
|
finds = {
|
|
160
172
|
query: options.findText,
|
|
161
|
-
total:
|
|
162
|
-
...(truncated ? { truncated: true } : {}),
|
|
163
|
-
scannedBlocks:
|
|
164
|
-
matches: matches.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
total: asNumber(scan?.total),
|
|
174
|
+
...(scan?.truncated === true ? { truncated: true } : {}),
|
|
175
|
+
scannedBlocks: asNumber(scan?.scannedBlocks),
|
|
176
|
+
matches: matches.map((match) => {
|
|
177
|
+
const rec = asRecord(match) ?? {};
|
|
178
|
+
const nodeId = asString(rec.nodeId);
|
|
179
|
+
return {
|
|
180
|
+
ordinal: asNumber(rec.ordinal) + 1,
|
|
181
|
+
...(nodeId ? { nodeId } : {}),
|
|
182
|
+
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
183
|
+
preview: asString(rec.preview),
|
|
184
|
+
};
|
|
185
|
+
}),
|
|
168
186
|
};
|
|
169
|
-
if (
|
|
170
|
-
blockOffset = Math.max(0,
|
|
187
|
+
if (typeof scan?.firstMatchOrdinal === 'number' && options.blockOffset == null) {
|
|
188
|
+
blockOffset = Math.max(0, scan.firstMatchOrdinal - 2);
|
|
171
189
|
blockLimit = options.blockLimit != null && options.blockLimit > 0 ? options.blockLimit : 12;
|
|
172
190
|
}
|
|
173
191
|
}
|
|
@@ -404,46 +422,7 @@ async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
404
422
|
const extractFn = maybeMethod(doc, ['extract']);
|
|
405
423
|
if (tables.length > 0 && extractFn) {
|
|
406
424
|
const extractRaw = await safeCall(() => extractFn({}), null, recordError('extract'));
|
|
407
|
-
|
|
408
|
-
const extractBlocks = Array.isArray(extractRec?.blocks) ? extractRec.blocks : [];
|
|
409
|
-
const cellsByTableNodeId = new Map();
|
|
410
|
-
for (const block of extractBlocks) {
|
|
411
|
-
const rec = asRecord(block);
|
|
412
|
-
const tableContext = asRecord(rec?.tableContext);
|
|
413
|
-
if (!rec || !tableContext)
|
|
414
|
-
continue;
|
|
415
|
-
const tableOrdinal = asNumber(tableContext.tableOrdinal, -1);
|
|
416
|
-
const rowIndex = asNumber(tableContext.rowIndex, -1);
|
|
417
|
-
const columnIndex = asNumber(tableContext.columnIndex, -1);
|
|
418
|
-
if (tableOrdinal < 0 || rowIndex < 0 || columnIndex < 0)
|
|
419
|
-
continue;
|
|
420
|
-
const table = tables[tableOrdinal - precedingTableCount];
|
|
421
|
-
if (!table)
|
|
422
|
-
continue;
|
|
423
|
-
const key = `${rowIndex}:${columnIndex}`;
|
|
424
|
-
const text = asString(rec.text);
|
|
425
|
-
const nodeId = asString(rec.nodeId) || undefined;
|
|
426
|
-
const cellMap = cellsByTableNodeId.get(table.nodeId) ??
|
|
427
|
-
new Map();
|
|
428
|
-
const existing = cellMap.get(key);
|
|
429
|
-
cellMap.set(key, {
|
|
430
|
-
rowIndex,
|
|
431
|
-
columnIndex,
|
|
432
|
-
text: existing == null || text.length === 0
|
|
433
|
-
? (existing?.text ?? text)
|
|
434
|
-
: existing.text.length === 0
|
|
435
|
-
? text
|
|
436
|
-
: `${existing.text}\n${text}`,
|
|
437
|
-
nodeId: existing?.nodeId ?? nodeId,
|
|
438
|
-
});
|
|
439
|
-
cellsByTableNodeId.set(table.nodeId, cellMap);
|
|
440
|
-
}
|
|
441
|
-
for (const table of tables) {
|
|
442
|
-
const cellMap = cellsByTableNodeId.get(table.nodeId);
|
|
443
|
-
if (!cellMap)
|
|
444
|
-
continue;
|
|
445
|
-
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
446
|
-
}
|
|
425
|
+
populateTableCellsFromExtract(tables, extractRaw, precedingTableCount);
|
|
447
426
|
}
|
|
448
427
|
// Optionally enrich table cells with per-run formatting (opt-in; one
|
|
449
428
|
// query.match per cell, bounded) so a reader can match a cell's pattern.
|
|
@@ -748,6 +727,144 @@ async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
748
727
|
...(finds ? { finds } : {}),
|
|
749
728
|
};
|
|
750
729
|
}
|
|
730
|
+
class MutationSnapshotError extends Error {
|
|
731
|
+
code;
|
|
732
|
+
constructor(code, message) {
|
|
733
|
+
super(message);
|
|
734
|
+
this.code = code;
|
|
735
|
+
this.name = 'MutationSnapshotError';
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const MUTATION_BLOCK_PAGE_SIZE = 1000;
|
|
739
|
+
const MUTATION_BLOCK_PAGE_CONCURRENCY = 4;
|
|
740
|
+
const MUTATION_NON_TABLE_DOMAINS = [
|
|
741
|
+
'blocks',
|
|
742
|
+
'lists',
|
|
743
|
+
'comments',
|
|
744
|
+
'trackedChanges',
|
|
745
|
+
'sections',
|
|
746
|
+
'headerFooters',
|
|
747
|
+
'styles',
|
|
748
|
+
'contentControls',
|
|
749
|
+
'fields',
|
|
750
|
+
'hyperlinks',
|
|
751
|
+
'bookmarks',
|
|
752
|
+
'permissionRanges',
|
|
753
|
+
'images',
|
|
754
|
+
];
|
|
755
|
+
/**
|
|
756
|
+
* Build the complete block index used to authorize mutations. Inspection
|
|
757
|
+
* snapshots stay windowed; mutation selectors must not treat that presentation
|
|
758
|
+
* window as the end of the document. Reads remain bounded and the revision is
|
|
759
|
+
* checked before the combined snapshot is returned.
|
|
760
|
+
*/
|
|
761
|
+
async function buildMutationSnapshot(doc, options = {}) {
|
|
762
|
+
const requestedDomains = options.includeDomains == null || options.includeDomains.length === 0 ? null : new Set(options.includeDomains);
|
|
763
|
+
const includeTables = requestedDomains == null || requestedDomains.has('tables');
|
|
764
|
+
const firstDomains = requestedDomains == null
|
|
765
|
+
? MUTATION_NON_TABLE_DOMAINS
|
|
766
|
+
: [...requestedDomains].filter((domain) => domain !== 'tables').concat('blocks');
|
|
767
|
+
const first = await buildDocumentSnapshot(doc, {
|
|
768
|
+
includeDomains: firstDomains,
|
|
769
|
+
blockOffset: 0,
|
|
770
|
+
blockLimit: MUTATION_BLOCK_PAGE_SIZE,
|
|
771
|
+
});
|
|
772
|
+
assertMutationBlockRead(first, 0);
|
|
773
|
+
const total = first.counts.blocks;
|
|
774
|
+
if (first.blocks.length > total) {
|
|
775
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: document reports ${total} blocks but returned ${first.blocks.length}`);
|
|
776
|
+
}
|
|
777
|
+
if (first.blocks.length === 0 && total > 0) {
|
|
778
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: document reports ${total} blocks but returned none`);
|
|
779
|
+
}
|
|
780
|
+
const blocks = [...first.blocks];
|
|
781
|
+
const diagnostics = [...first.diagnostics];
|
|
782
|
+
const pageStride = first.blocks.length || MUTATION_BLOCK_PAGE_SIZE;
|
|
783
|
+
const offsets = first.blocks.length === total
|
|
784
|
+
? []
|
|
785
|
+
: Array.from({ length: Math.ceil((total - pageStride) / pageStride) }, (_, index) => pageStride * (index + 1));
|
|
786
|
+
for (let index = 0; index < offsets.length; index += MUTATION_BLOCK_PAGE_CONCURRENCY) {
|
|
787
|
+
const batchOffsets = offsets.slice(index, index + MUTATION_BLOCK_PAGE_CONCURRENCY);
|
|
788
|
+
const pages = await Promise.all(batchOffsets.map((blockOffset) => buildDocumentSnapshot(doc, {
|
|
789
|
+
includeDomains: ['blocks'],
|
|
790
|
+
blockOffset,
|
|
791
|
+
blockLimit: pageStride,
|
|
792
|
+
})));
|
|
793
|
+
for (let pageIndex = 0; pageIndex < pages.length; pageIndex += 1) {
|
|
794
|
+
const page = pages[pageIndex];
|
|
795
|
+
const blockOffset = batchOffsets[pageIndex];
|
|
796
|
+
assertMutationBlockRead(page, blockOffset);
|
|
797
|
+
assertMutationSnapshotRevision(first.revision, page.revision);
|
|
798
|
+
if (page.blocks.length === 0) {
|
|
799
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: block pagination stopped at ${blockOffset} of ${total} blocks`);
|
|
800
|
+
}
|
|
801
|
+
blocks.push(...page.blocks);
|
|
802
|
+
diagnostics.push(...page.diagnostics);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (blocks.length !== total) {
|
|
806
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: expected ${total} blocks but materialized ${blocks.length}`);
|
|
807
|
+
}
|
|
808
|
+
const tables = includeTables ? await buildMutationTables(doc, blocks, diagnostics) : [];
|
|
809
|
+
const finalIdentity = await buildDocumentSnapshot(doc, { countsOnly: true });
|
|
810
|
+
assertMutationSnapshotRevision(first.revision, finalIdentity.revision);
|
|
811
|
+
return {
|
|
812
|
+
...first,
|
|
813
|
+
blocks,
|
|
814
|
+
tables,
|
|
815
|
+
diagnostics,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
async function buildMutationTables(doc, blocks, diagnostics) {
|
|
819
|
+
const tableBlocks = blocks.filter((block) => block.nodeType === 'table');
|
|
820
|
+
const tables = tableBlocks.map((block, index) => ({
|
|
821
|
+
nodeId: block.nodeId,
|
|
822
|
+
ordinal: index + 1,
|
|
823
|
+
rows: 0,
|
|
824
|
+
columns: 0,
|
|
825
|
+
cells: [],
|
|
826
|
+
}));
|
|
827
|
+
const getTable = maybeMethod(doc, ['tables', 'get']);
|
|
828
|
+
if (getTable) {
|
|
829
|
+
for (let index = 0; index < tables.length; index += MUTATION_BLOCK_PAGE_CONCURRENCY) {
|
|
830
|
+
const batch = tables.slice(index, index + MUTATION_BLOCK_PAGE_CONCURRENCY);
|
|
831
|
+
await Promise.all(batch.map(async (table) => {
|
|
832
|
+
try {
|
|
833
|
+
const raw = asRecord(await getTable({ nodeId: table.nodeId }));
|
|
834
|
+
table.rows = asNumber(raw?.rows);
|
|
835
|
+
table.columns = asNumber(raw?.columns);
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
diagnostics.push({
|
|
839
|
+
section: `tables.get:${table.nodeId}`,
|
|
840
|
+
message: error instanceof Error ? error.message : String(error),
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const extract = maybeMethod(doc, ['extract']);
|
|
847
|
+
if (tables.length > 0 && extract) {
|
|
848
|
+
try {
|
|
849
|
+
populateTableCellsFromExtract(tables, await extract({}));
|
|
850
|
+
}
|
|
851
|
+
catch (error) {
|
|
852
|
+
diagnostics.push({ section: 'extract', message: error instanceof Error ? error.message : String(error) });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return tables;
|
|
856
|
+
}
|
|
857
|
+
function assertMutationBlockRead(snapshot, blockOffset) {
|
|
858
|
+
const failure = snapshot.diagnostics.find((diagnostic) => diagnostic.section === 'blocks.list');
|
|
859
|
+
if (!failure)
|
|
860
|
+
return;
|
|
861
|
+
throw new MutationSnapshotError('SNAPSHOT_INCOMPLETE', `cannot build complete mutation snapshot: block page at offset ${blockOffset} failed: ${failure.message}`);
|
|
862
|
+
}
|
|
863
|
+
function assertMutationSnapshotRevision(expected, actual) {
|
|
864
|
+
if (expected === 'unknown' || actual === 'unknown' || expected === actual)
|
|
865
|
+
return;
|
|
866
|
+
throw new MutationSnapshotError('REVISION_CONFLICT', `document changed while resolving mutation selectors (${expected} -> ${actual}); retry the action`);
|
|
867
|
+
}
|
|
751
868
|
/**
|
|
752
869
|
* Structured ambiguity error returned when multiple candidates match a
|
|
753
870
|
* selector and the plan required uniqueness.
|
|
@@ -882,6 +999,8 @@ function resolveSnapshotSelector(snapshot, selector) {
|
|
|
882
999
|
}
|
|
883
1000
|
|
|
884
1001
|
exports.AmbiguousSelectorError = AmbiguousSelectorError;
|
|
1002
|
+
exports.MutationSnapshotError = MutationSnapshotError;
|
|
885
1003
|
exports.buildDocumentSnapshot = buildDocumentSnapshot;
|
|
1004
|
+
exports.buildMutationSnapshot = buildMutationSnapshot;
|
|
886
1005
|
exports.matchRunsForBlock = matchRunsForBlock;
|
|
887
1006
|
exports.resolveSnapshotSelector = resolveSnapshotSelector;
|
|
@@ -313,6 +313,17 @@ type SnapshotOptions = {
|
|
|
313
313
|
* rest of the snapshot remains usable.
|
|
314
314
|
*/
|
|
315
315
|
export declare function buildDocumentSnapshot(doc: BoundDocApi, options?: SnapshotOptions): Promise<DocumentSnapshot>;
|
|
316
|
+
export declare class MutationSnapshotError extends Error {
|
|
317
|
+
readonly code: 'SNAPSHOT_INCOMPLETE' | 'REVISION_CONFLICT';
|
|
318
|
+
constructor(code: 'SNAPSHOT_INCOMPLETE' | 'REVISION_CONFLICT', message: string);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Build the complete block index used to authorize mutations. Inspection
|
|
322
|
+
* snapshots stay windowed; mutation selectors must not treat that presentation
|
|
323
|
+
* window as the end of the document. Reads remain bounded and the revision is
|
|
324
|
+
* checked before the combined snapshot is returned.
|
|
325
|
+
*/
|
|
326
|
+
export declare function buildMutationSnapshot(doc: BoundDocApi, options?: Pick<SnapshotOptions, 'includeDomains'>): Promise<DocumentSnapshot>;
|
|
316
327
|
/**
|
|
317
328
|
* Structured ambiguity error returned when multiple candidates match a
|
|
318
329
|
* selector and the plan required uniqueness.
|