@superdoc/sdk 2.25.0-next.8 → 2.25.0

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.
@@ -2,12 +2,22 @@
2
2
 
3
3
  var documentEvidence = require('../runtime/document-evidence.cjs');
4
4
  var operationCatalog = require('./operation-catalog.cjs');
5
+ var contract = require('../generated/contract.cjs');
6
+ var errors = require('../runtime/errors.cjs');
5
7
 
6
8
  const unboundDocuments = new WeakMap();
7
9
  function withoutActionChangeMode(document) {
8
10
  return unboundDocuments.get(document) ?? document;
9
11
  }
10
12
  function bindActionChangeMode(document, changeMode) {
13
+ return bindActionInvokeOptions(document, { changeMode });
14
+ }
15
+ function bindActionExpectedRevision(document, expectedRevision) {
16
+ return bindActionInvokeOptions(document, { expectedRevision });
17
+ }
18
+ function bindActionInvokeOptions(document, invocation) {
19
+ let pendingRevision = invocation.expectedRevision;
20
+ const changeMode = invocation.changeMode;
11
21
  const wrap = (value, path, owner) => new Proxy(value, {
12
22
  get(target, property, receiver) {
13
23
  const member = Reflect.get(target, property, receiver);
@@ -15,12 +25,39 @@ function bindActionChangeMode(document, changeMode) {
15
25
  return member;
16
26
  const memberPath = [...path, property];
17
27
  if (typeof member === 'function' && operationCatalog.getOperationCatalogEntry(`doc.${memberPath.join('.')}`)?.isMutating) {
18
- return (input = {}, options) => Reflect.apply(member, target, [
19
- // CLI inputs can already contain a mode. Keep it consistent with
20
- // the options used by in-process Document API hosts.
21
- input.changeMode === undefined ? input : { ...input, changeMode },
22
- { ...options, changeMode },
23
- ]);
28
+ return (input = {}, options) => {
29
+ let expectedRevision = pendingRevision;
30
+ if (expectedRevision !== undefined) {
31
+ const spec = contract.CONTRACT.operations[`doc.${memberPath.join('.')}`]?.params.find((param) => param.name === 'expectedRevision');
32
+ if (spec?.type === 'number') {
33
+ const numeric = Number(expectedRevision);
34
+ if (!/^(0|[1-9]\d*)$/.test(expectedRevision) || !Number.isSafeInteger(numeric))
35
+ throw new errors.SuperDocCliError('This operation requires a canonical numeric revision.', {
36
+ code: 'INVALID_ARGUMENT',
37
+ });
38
+ expectedRevision = numeric;
39
+ }
40
+ else if (spec?.type !== 'string') {
41
+ throw new errors.SuperDocCliError('This operation cannot enforce an expected revision.', {
42
+ code: 'CAPABILITY_UNSUPPORTED',
43
+ });
44
+ }
45
+ pendingRevision = undefined;
46
+ }
47
+ return Reflect.apply(member, target, [
48
+ // CLI inputs can already contain a mode. Keep it consistent with
49
+ // the options used by in-process Document API hosts.
50
+ {
51
+ ...(input.changeMode === undefined || changeMode === undefined ? input : { ...input, changeMode }),
52
+ ...(expectedRevision === undefined ? {} : { expectedRevision }),
53
+ },
54
+ {
55
+ ...options,
56
+ ...(changeMode === undefined ? {} : { changeMode }),
57
+ ...(expectedRevision === undefined ? {} : { expectedRevision }),
58
+ },
59
+ ]);
60
+ };
24
61
  }
25
62
  if (member && (typeof member === 'object' || typeof member === 'function')) {
26
63
  return wrap(member, memberPath, target);
@@ -38,4 +75,5 @@ function bindActionChangeMode(document, changeMode) {
38
75
  }
39
76
 
40
77
  exports.bindActionChangeMode = bindActionChangeMode;
78
+ exports.bindActionExpectedRevision = bindActionExpectedRevision;
41
79
  exports.withoutActionChangeMode = withoutActionChangeMode;
@@ -2,3 +2,4 @@ import type { BoundDocApi } from '../generated/client.js';
2
2
  import type { AgentChangeMode } from './ir.js';
3
3
  export declare function withoutActionChangeMode(document: BoundDocApi): BoundDocApi;
4
4
  export declare function bindActionChangeMode(document: BoundDocApi, changeMode: AgentChangeMode): BoundDocApi;
5
+ export declare function bindActionExpectedRevision(document: BoundDocApi, expectedRevision: string): BoundDocApi;
@@ -1,10 +1,20 @@
1
1
  import { forwardDocumentEvidence } from '../runtime/document-evidence.js';
2
2
  import { getOperationCatalogEntry } from './operation-catalog.js';
3
+ import { CONTRACT } from '../generated/contract.js';
4
+ import { SuperDocCliError } from '../runtime/errors.js';
3
5
  const unboundDocuments = new WeakMap();
4
6
  export function withoutActionChangeMode(document) {
5
7
  return unboundDocuments.get(document) ?? document;
6
8
  }
7
9
  export function bindActionChangeMode(document, changeMode) {
10
+ return bindActionInvokeOptions(document, { changeMode });
11
+ }
12
+ export function bindActionExpectedRevision(document, expectedRevision) {
13
+ return bindActionInvokeOptions(document, { expectedRevision });
14
+ }
15
+ function bindActionInvokeOptions(document, invocation) {
16
+ let pendingRevision = invocation.expectedRevision;
17
+ const changeMode = invocation.changeMode;
8
18
  const wrap = (value, path, owner) => new Proxy(value, {
9
19
  get(target, property, receiver) {
10
20
  const member = Reflect.get(target, property, receiver);
@@ -12,12 +22,39 @@ export function bindActionChangeMode(document, changeMode) {
12
22
  return member;
13
23
  const memberPath = [...path, property];
14
24
  if (typeof member === 'function' && getOperationCatalogEntry(`doc.${memberPath.join('.')}`)?.isMutating) {
15
- return (input = {}, options) => Reflect.apply(member, target, [
16
- // CLI inputs can already contain a mode. Keep it consistent with
17
- // the options used by in-process Document API hosts.
18
- input.changeMode === undefined ? input : { ...input, changeMode },
19
- { ...options, changeMode },
20
- ]);
25
+ return (input = {}, options) => {
26
+ let expectedRevision = pendingRevision;
27
+ if (expectedRevision !== undefined) {
28
+ const spec = CONTRACT.operations[`doc.${memberPath.join('.')}`]?.params.find((param) => param.name === 'expectedRevision');
29
+ if (spec?.type === 'number') {
30
+ const numeric = Number(expectedRevision);
31
+ if (!/^(0|[1-9]\d*)$/.test(expectedRevision) || !Number.isSafeInteger(numeric))
32
+ throw new SuperDocCliError('This operation requires a canonical numeric revision.', {
33
+ code: 'INVALID_ARGUMENT',
34
+ });
35
+ expectedRevision = numeric;
36
+ }
37
+ else if (spec?.type !== 'string') {
38
+ throw new SuperDocCliError('This operation cannot enforce an expected revision.', {
39
+ code: 'CAPABILITY_UNSUPPORTED',
40
+ });
41
+ }
42
+ pendingRevision = undefined;
43
+ }
44
+ return Reflect.apply(member, target, [
45
+ // CLI inputs can already contain a mode. Keep it consistent with
46
+ // the options used by in-process Document API hosts.
47
+ {
48
+ ...(input.changeMode === undefined || changeMode === undefined ? input : { ...input, changeMode }),
49
+ ...(expectedRevision === undefined ? {} : { expectedRevision }),
50
+ },
51
+ {
52
+ ...options,
53
+ ...(changeMode === undefined ? {} : { changeMode }),
54
+ ...(expectedRevision === undefined ? {} : { expectedRevision }),
55
+ },
56
+ ]);
57
+ };
21
58
  }
22
59
  if (member && (typeof member === 'object' || typeof member === 'function')) {
23
60
  return wrap(member, memberPath, target);
@@ -1111,7 +1111,7 @@ function granularGlobalEvidence({ applied }, skippedEdits, requested) {
1111
1111
  };
1112
1112
  }
1113
1113
  async function runGlobalReplace(doc, args, pre) {
1114
- assertExpectedReplacementRevision(args.expectedRevision, pre.revision);
1114
+ assertExpectedActionRevision(args.expectedRevision, pre.revision);
1115
1115
  assertReplacementTextPayloads(args.edits);
1116
1116
  if (args.edits.length === 0) {
1117
1117
  return failedReceipt('replace_text', new Error('edits must be non-empty'), pre);
@@ -4779,12 +4779,17 @@ function parseReplacementGranularity(value) {
4779
4779
  }
4780
4780
  return granularity;
4781
4781
  }
4782
- function assertExpectedReplacementRevision(expected, actual) {
4783
- if (expected != null && expected !== actual) {
4784
- throw new errors.SuperDocCliError('replace_text: expectedRevision is stale. Inspect this live document again before editing.', {
4785
- code: 'REVISION_CONFLICT',
4786
- });
4787
- }
4782
+ function staleRevisionError(action) {
4783
+ return new errors.SuperDocCliError(`${action}: expectedRevision is stale. Inspect this live document again before editing.`, {
4784
+ code: 'REVISION_CONFLICT',
4785
+ });
4786
+ }
4787
+ function assertExpectedActionRevision(expected, actual, action = 'replace_text') {
4788
+ if (expected != null && expected !== actual)
4789
+ throw staleRevisionError(action);
4790
+ }
4791
+ function staleActionRevisionReceipt(action, liveRevision) {
4792
+ return { ...failedReceipt(action, staleRevisionError(action)), preSnapshot: { revision: liveRevision } };
4788
4793
  }
4789
4794
  // Ranges of the block text that may show a body field's result. Field
4790
4795
  // results carry no offsets, so every place the result text occurs counts,
@@ -4825,7 +4830,7 @@ async function fieldResultRanges(doc, nodeId, blockText) {
4825
4830
  async function resolveScopedReplaceTarget(doc, args) {
4826
4831
  assertReplacementTextPayloads(args.edits);
4827
4832
  const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
4828
- assertExpectedReplacementRevision(args.expectedRevision, pre.revision);
4833
+ assertExpectedActionRevision(args.expectedRevision, pre.revision);
4829
4834
  const matched = docSnapshot.resolveSnapshotSelector(pre, args.selector);
4830
4835
  if (matched.length !== 1) {
4831
4836
  const failure = describeSelectorFailure(args.selector, matched, pre, args.edits.map((e) => e.find));
@@ -7990,10 +7995,12 @@ exports.ACTION_DISPOSITIONS = ACTION_DISPOSITIONS;
7990
7995
  exports.ACTION_GROUPS = ACTION_GROUPS;
7991
7996
  exports.ACTION_HINTS = ACTION_HINTS;
7992
7997
  exports.ACTION_NAMES_LIST = ACTION_NAMES_LIST;
7998
+ exports.assertExpectedActionRevision = assertExpectedActionRevision;
7993
7999
  exports.getActionDispositions = getActionDispositions;
7994
8000
  exports.getUnavailableTrackedActions = getUnavailableTrackedActions;
7995
8001
  exports.isActionName = isActionName;
7996
8002
  exports.markerShapeOf = markerShapeOf;
8003
+ exports.staleActionRevisionReceipt = staleActionRevisionReceipt;
7997
8004
  exports.superdocPerformAction = superdocPerformAction;
7998
8005
  exports.typedCaptionSpanOf = typedCaptionSpanOf;
7999
8006
  exports.typedMarkerPrefixOf = typedMarkerPrefixOf;
@@ -493,6 +493,8 @@ export type AddHyperlinkArgs = {
493
493
  url: string;
494
494
  tooltip?: string;
495
495
  };
496
+ export declare function assertExpectedActionRevision(expected: string | undefined, actual: string, action?: string): void;
497
+ export declare function staleActionRevisionReceipt(action: string, liveRevision: string): AgentReceipt;
496
498
  export declare function typedMarkerPrefixOf(text: string): {
497
499
  prefix: string;
498
500
  marker: string;
@@ -1108,7 +1108,7 @@ function granularGlobalEvidence({ applied }, skippedEdits, requested) {
1108
1108
  };
1109
1109
  }
1110
1110
  async function runGlobalReplace(doc, args, pre) {
1111
- assertExpectedReplacementRevision(args.expectedRevision, pre.revision);
1111
+ assertExpectedActionRevision(args.expectedRevision, pre.revision);
1112
1112
  assertReplacementTextPayloads(args.edits);
1113
1113
  if (args.edits.length === 0) {
1114
1114
  return failedReceipt('replace_text', new Error('edits must be non-empty'), pre);
@@ -4776,12 +4776,17 @@ function parseReplacementGranularity(value) {
4776
4776
  }
4777
4777
  return granularity;
4778
4778
  }
4779
- function assertExpectedReplacementRevision(expected, actual) {
4780
- if (expected != null && expected !== actual) {
4781
- throw new SuperDocCliError('replace_text: expectedRevision is stale. Inspect this live document again before editing.', {
4782
- code: 'REVISION_CONFLICT',
4783
- });
4784
- }
4779
+ function staleRevisionError(action) {
4780
+ return new SuperDocCliError(`${action}: expectedRevision is stale. Inspect this live document again before editing.`, {
4781
+ code: 'REVISION_CONFLICT',
4782
+ });
4783
+ }
4784
+ export function assertExpectedActionRevision(expected, actual, action = 'replace_text') {
4785
+ if (expected != null && expected !== actual)
4786
+ throw staleRevisionError(action);
4787
+ }
4788
+ export function staleActionRevisionReceipt(action, liveRevision) {
4789
+ return { ...failedReceipt(action, staleRevisionError(action)), preSnapshot: { revision: liveRevision } };
4785
4790
  }
4786
4791
  // Ranges of the block text that may show a body field's result. Field
4787
4792
  // results carry no offsets, so every place the result text occurs counts,
@@ -4822,7 +4827,7 @@ async function fieldResultRanges(doc, nodeId, blockText) {
4822
4827
  async function resolveScopedReplaceTarget(doc, args) {
4823
4828
  assertReplacementTextPayloads(args.edits);
4824
4829
  const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
4825
- assertExpectedReplacementRevision(args.expectedRevision, pre.revision);
4830
+ assertExpectedActionRevision(args.expectedRevision, pre.revision);
4826
4831
  const matched = resolveSnapshotSelector(pre, args.selector);
4827
4832
  if (matched.length !== 1) {
4828
4833
  const failure = describeSelectorFailure(args.selector, matched, pre, args.edits.map((e) => e.find));