@sanity/runtime-cli 17.0.0 → 17.1.0-next.g3cb2d2e

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.
@@ -64,6 +64,6 @@
64
64
  <script src="./components/clear-button.js" type="module"></script>
65
65
  <script src="./components/help-button.js" type="module"></script>
66
66
  <script src="./components/fetch-button.js" type="module"></script>
67
- <script src="./hot-reload.js" type="module"></script>
67
+ <script src="./web-socket.js" type="module"></script>
68
68
  </body>
69
69
  </html>
@@ -4515,7 +4515,7 @@ function nextClusterBreak(str, pos, includeExtending) {
4515
4515
  }
4516
4516
 
4517
4517
  function prevClusterBreak(str, pos, includeExtending) {
4518
- while (pos > 0) {
4518
+ while (pos > 1) {
4519
4519
  let found = nextClusterBreak(str, pos - 2, includeExtending);
4520
4520
  if (found < pos) return found
4521
4521
  pos--;
@@ -5797,10 +5797,18 @@ class SelectionRange {
5797
5797
  /**
5798
5798
  The upper boundary of the range.
5799
5799
  */
5800
- to, flags) {
5800
+ to, flags,
5801
+ /**
5802
+ The goal column (stored vertical offset) associated with a
5803
+ cursor. This is used to preserve the vertical position when
5804
+ [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across
5805
+ lines of different length.
5806
+ */
5807
+ goalColumn) {
5801
5808
  this.from = from;
5802
5809
  this.to = to;
5803
5810
  this.flags = flags;
5811
+ this.goalColumn = goalColumn;
5804
5812
  }
5805
5813
  /**
5806
5814
  The anchor of the range—the side that doesn't move when you
@@ -5824,6 +5832,16 @@ class SelectionRange {
5824
5832
  */
5825
5833
  get assoc() { return this.flags & 8 /* RangeFlag.AssocBefore */ ? -1 : this.flags & 16 /* RangeFlag.AssocAfter */ ? 1 : 0; }
5826
5834
  /**
5835
+ A flag that, when set, makes some selection-extending commands
5836
+ treat the range's head and anchor as exchangeable, so that for
5837
+ example Shift-ArrowUp will make the lower side of the selection
5838
+ the anchor, even if that was the head before. Used to implement
5839
+ MacOS-style undirectional selections.
5840
+ */
5841
+ get undirectional() {
5842
+ return (this.flags & 64 /* RangeFlag.Undirectional */) > 0;
5843
+ }
5844
+ /**
5827
5845
  The bidirectional text level associated with this cursor, if
5828
5846
  any.
5829
5847
  */
@@ -5832,16 +5850,6 @@ class SelectionRange {
5832
5850
  return level == 7 ? null : level;
5833
5851
  }
5834
5852
  /**
5835
- The goal column (stored vertical offset) associated with a
5836
- cursor. This is used to preserve the vertical position when
5837
- [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across
5838
- lines of different length.
5839
- */
5840
- get goalColumn() {
5841
- let value = this.flags >> 6 /* RangeFlag.GoalColumnOffset */;
5842
- return value == 16777215 /* RangeFlag.NoGoalColumn */ ? undefined : value;
5843
- }
5844
- /**
5845
5853
  Map this range through a change, producing a valid range in the
5846
5854
  updated document.
5847
5855
  */
@@ -5854,7 +5862,7 @@ class SelectionRange {
5854
5862
  from = change.mapPos(this.from, 1);
5855
5863
  to = change.mapPos(this.to, -1);
5856
5864
  }
5857
- return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags);
5865
+ return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags, this.goalColumn);
5858
5866
  }
5859
5867
  /**
5860
5868
  Extend this range to cover at least `from` to `to`.
@@ -5888,8 +5896,8 @@ class SelectionRange {
5888
5896
  /**
5889
5897
  @internal
5890
5898
  */
5891
- static create(from, to, flags) {
5892
- return new SelectionRange(from, to, flags);
5899
+ static create(from, to, flags, goalColumn) {
5900
+ return new SelectionRange(from, to, flags, goalColumn);
5893
5901
  }
5894
5902
  }
5895
5903
  /**
@@ -6004,19 +6012,26 @@ class EditorSelection {
6004
6012
  */
6005
6013
  static cursor(pos, assoc = 0, bidiLevel, goalColumn) {
6006
6014
  return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) |
6007
- (bidiLevel == null ? 7 : Math.min(6, bidiLevel)) |
6008
- ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */));
6015
+ (bidiLevel == null ? 7 : Math.min(6, bidiLevel)), goalColumn);
6009
6016
  }
6010
6017
  /**
6011
6018
  Create a selection range.
6012
6019
  */
6013
6020
  static range(anchor, head, goalColumn, bidiLevel, assoc) {
6014
- let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */) |
6015
- (bidiLevel == null ? 7 : Math.min(6, bidiLevel));
6021
+ let flags = bidiLevel == null ? 7 : Math.min(6, bidiLevel);
6016
6022
  if (!assoc && anchor != head)
6017
6023
  assoc = head < anchor ? 1 : -1;
6018
- return head < anchor ? SelectionRange.create(head, anchor, 32 /* RangeFlag.Inverted */ | 16 /* RangeFlag.AssocAfter */ | flags)
6019
- : SelectionRange.create(anchor, head, (!assoc ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) | flags);
6024
+ if (assoc)
6025
+ flags |= assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */;
6026
+ return head < anchor ? SelectionRange.create(head, anchor, flags | 32 /* RangeFlag.Inverted */, goalColumn)
6027
+ : SelectionRange.create(anchor, head, flags, goalColumn);
6028
+ }
6029
+ /**
6030
+ Create an [undirectional](https://codemirror.net/6/docs/ref/#state.SelectionRange.undirectional)
6031
+ selection range.
6032
+ */
6033
+ static undirectionalRange(from, to) {
6034
+ return SelectionRange.create(from, to, 64 /* RangeFlag.Undirectional */, undefined);
6020
6035
  }
6021
6036
  /**
6022
6037
  @internal
@@ -11521,10 +11536,12 @@ class TileUpdate {
11521
11536
  }
11522
11537
  pendingLineAttrs = null;
11523
11538
  }
11539
+ markCount = active.length;
11524
11540
  }
11525
11541
  });
11526
- b.addLineStartIfNotCovered(pendingLineAttrs);
11527
11542
  this.openWidget = openEnd > markCount;
11543
+ if (!this.openWidget)
11544
+ b.addLineStartIfNotCovered(pendingLineAttrs);
11528
11545
  this.openMarks = openEnd;
11529
11546
  }
11530
11547
  forward(from, to, side = 1) {
@@ -12349,7 +12366,7 @@ function groupAt(state, pos, bias = 1) {
12349
12366
  break;
12350
12367
  to = next;
12351
12368
  }
12352
- return EditorSelection.range(from + line.from, to + line.from);
12369
+ return EditorSelection.undirectionalRange(from + line.from, to + line.from);
12353
12370
  }
12354
12371
  function posAtCoordsImprecise(view, contentRect, block, x, y) {
12355
12372
  let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
@@ -12486,8 +12503,12 @@ function skipAtomsForSelection(atoms, sel) {
12486
12503
  else {
12487
12504
  let from = skipAtomicRanges(atoms, range.from, -1);
12488
12505
  let to = skipAtomicRanges(atoms, range.to, 1);
12489
- if (from != range.from || to != range.to)
12490
- updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
12506
+ if (from != range.from || to != range.to) {
12507
+ if (range.undirectional)
12508
+ updated = EditorSelection.undirectionalRange(range.from, range.to);
12509
+ else
12510
+ updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
12511
+ }
12491
12512
  }
12492
12513
  if (updated) {
12493
12514
  if (!ranges)
@@ -12893,7 +12914,8 @@ class DOMChange {
12893
12914
  // Chrome will put the selection *inside* them, confusing
12894
12915
  // posFromDOM
12895
12916
  let vp = view.viewport;
12896
- if ((browser.ios || browser.chrome) && curSel.main.empty && head != anchor &&
12917
+ if ((browser.ios || browser.chrome) && head != anchor &&
12918
+ Math.min(head, anchor) <= curSel.main.from && Math.max(head, anchor) >= curSel.main.to &&
12897
12919
  (vp.from > 0 || vp.to < view.state.doc.length)) {
12898
12920
  let from = Math.min(head, anchor), to = Math.max(head, anchor);
12899
12921
  let offFrom = vp.from - from, offTo = vp.to - to;
@@ -13214,13 +13236,11 @@ class InputState {
13214
13236
  // (after which we retroactively handle them and reset the DOM) to
13215
13237
  // avoid messing up the virtual keyboard state.
13216
13238
  this.pendingIOSKey = undefined;
13217
- /**
13218
- When enabled (>-1), tab presses are not given to key handlers,
13219
- leaving the browser's default behavior. If >0, the mode expires
13220
- at that timestamp, and any other keypress clears it.
13221
- Esc enables temporary tab focus mode for two seconds when not
13222
- otherwise handled.
13223
- */
13239
+ // When enabled (>-1), tab presses are not given to key handlers,
13240
+ // leaving the browser's default behavior. If >0, the mode expires
13241
+ // at that timestamp, and any other keypress clears it.
13242
+ // Esc enables temporary tab focus mode for two seconds when not
13243
+ // otherwise handled.
13224
13244
  this.tabFocusMode = -1;
13225
13245
  this.lastSelectionOrigin = null;
13226
13246
  this.lastSelectionTime = 0;
@@ -13328,11 +13348,16 @@ class InputState {
13328
13348
  // state. So we let it go through, and then, in
13329
13349
  // applyDOMChange, notify key handlers of it and reset to
13330
13350
  // the state they produce.
13331
- let pending;
13332
- if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey && !event.shiftKey &&
13333
- ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
13351
+ if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
13352
+ (PendingKeys.some(key => key.keyCode == event.keyCode) && !event.ctrlKey ||
13334
13353
  EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey)) {
13335
- this.pendingIOSKey = pending || event;
13354
+ let mods = { ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey };
13355
+ // On iOS with autocapitalize, drop the shift modifier for these
13356
+ // keys, since it will be set at the start of every sentence.
13357
+ if (mods.shiftKey && browser.ios && !/^(off|none)$/.test(this.view.contentDOM.autocapitalize) &&
13358
+ iosVirtualKeyboardOpen(this.view.win))
13359
+ mods.shiftKey = false;
13360
+ this.pendingIOSKey = { key: event.key, keyCode: event.keyCode, mods };
13336
13361
  setTimeout(() => this.flushIOSKey(), 250);
13337
13362
  return true;
13338
13363
  }
@@ -13348,7 +13373,7 @@ class InputState {
13348
13373
  if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
13349
13374
  return false;
13350
13375
  this.pendingIOSKey = undefined;
13351
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
13376
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key.mods);
13352
13377
  }
13353
13378
  ignoreDuringComposition(event) {
13354
13379
  if (!/^key/.test(event.type) || event.synthetic)
@@ -13386,6 +13411,11 @@ class InputState {
13386
13411
  this.mouseSelection.destroy();
13387
13412
  }
13388
13413
  }
13414
+ function iosVirtualKeyboardOpen(win) {
13415
+ if (!win.visualViewport)
13416
+ return false;
13417
+ return win.visualViewport.height * win.visualViewport.scale / win.document.documentElement.clientHeight < 0.85;
13418
+ }
13389
13419
  function bindHandler(plugin, handler) {
13390
13420
  return (view, event) => {
13391
13421
  try {
@@ -13705,7 +13735,7 @@ function rangeForClick(view, pos, bias, type) {
13705
13735
  let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
13706
13736
  if (to < view.state.doc.length && to == line.to)
13707
13737
  to++;
13708
- return EditorSelection.range(from, to);
13738
+ return EditorSelection.undirectionalRange(from, to);
13709
13739
  }
13710
13740
  }
13711
13741
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -13763,7 +13793,7 @@ handlers.dragstart = (view, event) => {
13763
13793
  if (tile && tile.isWidget()) {
13764
13794
  let from = tile.posAtStart, to = from + tile.length;
13765
13795
  if (from >= range.to || to <= range.from)
13766
- range = EditorSelection.range(from, to);
13796
+ range = EditorSelection.undirectionalRange(from, to);
13767
13797
  }
13768
13798
  }
13769
13799
  let { inputState } = view;
@@ -16388,7 +16418,6 @@ class EditContextManager {
16388
16418
  for (let event in this.handlers)
16389
16419
  context.addEventListener(event, this.handlers[event]);
16390
16420
  this.measureReq = { read: view => {
16391
- this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
16392
16421
  let sel = getSelection(view.root);
16393
16422
  if (sel && sel.rangeCount)
16394
16423
  this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
@@ -20524,8 +20553,7 @@ function topNodeAt(state, pos, side) {
20524
20553
  }
20525
20554
  /**
20526
20555
  A subclass of [`Language`](https://codemirror.net/6/docs/ref/#language.Language) for use with Lezer
20527
- [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser)
20528
- parsers.
20556
+ [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser).
20529
20557
  */
20530
20558
  class LRLanguage extends Language {
20531
20559
  constructor(data, parser, name) {
@@ -21498,18 +21526,25 @@ const foldState = /*@__PURE__*/StateField.define({
21498
21526
  if (tr.isUserEvent("delete"))
21499
21527
  tr.changes.iterChangedRanges((fromA, toA) => folded = clearTouchedFolds(folded, fromA, toA));
21500
21528
  folded = folded.map(tr.changes);
21529
+ let rangesToFold = [];
21501
21530
  for (let e of tr.effects) {
21502
21531
  if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to)) {
21503
- let { preparePlaceholder } = tr.state.facet(foldConfig);
21504
- let widget = !preparePlaceholder ? foldWidget :
21505
- Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, e.value)) });
21506
- folded = folded.update({ add: [widget.range(e.value.from, e.value.to)] });
21532
+ rangesToFold.push(e.value);
21507
21533
  }
21508
21534
  else if (e.is(unfoldEffect)) {
21509
21535
  folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
21510
21536
  filterFrom: e.value.from, filterTo: e.value.to });
21511
21537
  }
21512
21538
  }
21539
+ if (rangesToFold.length) {
21540
+ let { preparePlaceholder } = tr.state.facet(foldConfig);
21541
+ let decorations = rangesToFold.map(value => {
21542
+ let widget = !preparePlaceholder ? foldWidget :
21543
+ Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, value)) });
21544
+ return widget.range(value.from, value.to);
21545
+ });
21546
+ folded = folded.update({ add: decorations });
21547
+ }
21513
21548
  // Clear folded ranges that cover the selection head
21514
21549
  if (tr.selection)
21515
21550
  folded = clearTouchedFolds(folded, tr.selection.main.head);
@@ -21825,7 +21860,7 @@ class HighlightStyle {
21825
21860
  or array of tags in their `tag` property, and either a single
21826
21861
  `class` property providing a static CSS class (for highlighter
21827
21862
  that rely on external styling), or a
21828
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)-style
21863
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)-style
21829
21864
  set of CSS properties (which define the styling for those tags).
21830
21865
 
21831
21866
  The CSS rules created for a highlighter will be emitted in the
@@ -23038,8 +23073,10 @@ Move the selection to the bracket matching the one it is currently
23038
23073
  on, if any.
23039
23074
  */
23040
23075
  const cursorMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state, dispatch);
23041
- function extendSel(target, how) {
23076
+ function extendSel(target, forward, how) {
23042
23077
  let selection = updateSel(target.state.selection, range => {
23078
+ if (range.undirectional && (range.head >= range.anchor) != forward)
23079
+ range = EditorSelection.range(range.head, range.anchor);
23043
23080
  let head = how(range);
23044
23081
  return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined, head.assoc);
23045
23082
  });
@@ -23049,7 +23086,7 @@ function extendSel(target, how) {
23049
23086
  return true;
23050
23087
  }
23051
23088
  function selectByChar(view, forward) {
23052
- return extendSel(view, range => view.moveByChar(range, forward));
23089
+ return extendSel(view, forward, range => view.moveByChar(range, forward));
23053
23090
  }
23054
23091
  /**
23055
23092
  Move the selection head one character to the left, while leaving
@@ -23061,7 +23098,7 @@ Move the selection head one character to the right.
23061
23098
  */
23062
23099
  const selectCharRight = view => selectByChar(view, ltrAtCursor(view));
23063
23100
  function selectByGroup(view, forward) {
23064
- return extendSel(view, range => view.moveByGroup(range, forward));
23101
+ return extendSel(view, forward, range => view.moveByGroup(range, forward));
23065
23102
  }
23066
23103
  /**
23067
23104
  Move the selection head one [group](https://codemirror.net/6/docs/ref/#commands.cursorGroupLeft) to
@@ -23075,13 +23112,19 @@ const selectGroupRight = view => selectByGroup(view, ltrAtCursor(view));
23075
23112
  /**
23076
23113
  Move the selection head over the next syntactic element to the left.
23077
23114
  */
23078
- const selectSyntaxLeft = view => extendSel(view, range => moveBySyntax(view.state, range, !ltrAtCursor(view)));
23115
+ const selectSyntaxLeft = view => {
23116
+ let forward = !ltrAtCursor(view);
23117
+ return extendSel(view, forward, range => moveBySyntax(view.state, range, forward));
23118
+ };
23079
23119
  /**
23080
23120
  Move the selection head over the next syntactic element to the right.
23081
23121
  */
23082
- const selectSyntaxRight = view => extendSel(view, range => moveBySyntax(view.state, range, ltrAtCursor(view)));
23122
+ const selectSyntaxRight = view => {
23123
+ let forward = ltrAtCursor(view);
23124
+ return extendSel(view, forward, range => moveBySyntax(view.state, range, forward));
23125
+ };
23083
23126
  function selectByLine(view, forward) {
23084
- return extendSel(view, range => view.moveVertically(range, forward));
23127
+ return extendSel(view, forward, range => view.moveVertically(range, forward));
23085
23128
  }
23086
23129
  /**
23087
23130
  Move the selection head one line up.
@@ -23092,7 +23135,7 @@ Move the selection head one line down.
23092
23135
  */
23093
23136
  const selectLineDown = view => selectByLine(view, true);
23094
23137
  function selectByPage(view, forward) {
23095
- return extendSel(view, range => view.moveVertically(range, forward, pageInfo(view).height));
23138
+ return extendSel(view, forward, range => view.moveVertically(range, forward, pageInfo(view).height));
23096
23139
  }
23097
23140
  /**
23098
23141
  Move the selection head one page up.
@@ -23105,27 +23148,33 @@ const selectPageDown = view => selectByPage(view, true);
23105
23148
  /**
23106
23149
  Move the selection head to the next line boundary.
23107
23150
  */
23108
- const selectLineBoundaryForward = view => extendSel(view, range => moveByLineBoundary(view, range, true));
23151
+ const selectLineBoundaryForward = view => extendSel(view, true, range => moveByLineBoundary(view, range, true));
23109
23152
  /**
23110
23153
  Move the selection head to the previous line boundary.
23111
23154
  */
23112
- const selectLineBoundaryBackward = view => extendSel(view, range => moveByLineBoundary(view, range, false));
23155
+ const selectLineBoundaryBackward = view => extendSel(view, false, range => moveByLineBoundary(view, range, false));
23113
23156
  /**
23114
23157
  Move the selection head one line boundary to the left.
23115
23158
  */
23116
- const selectLineBoundaryLeft = view => extendSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
23159
+ const selectLineBoundaryLeft = view => {
23160
+ let forward = !ltrAtCursor(view);
23161
+ return extendSel(view, forward, range => moveByLineBoundary(view, range, forward));
23162
+ };
23117
23163
  /**
23118
23164
  Move the selection head one line boundary to the right.
23119
23165
  */
23120
- const selectLineBoundaryRight = view => extendSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
23166
+ const selectLineBoundaryRight = view => {
23167
+ let forward = ltrAtCursor(view);
23168
+ return extendSel(view, forward, range => moveByLineBoundary(view, range, forward));
23169
+ };
23121
23170
  /**
23122
23171
  Move the selection head to the start of the line.
23123
23172
  */
23124
- const selectLineStart = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from));
23173
+ const selectLineStart = view => extendSel(view, false, range => EditorSelection.cursor(view.lineBlockAt(range.head).from));
23125
23174
  /**
23126
23175
  Move the selection head to the end of the line.
23127
23176
  */
23128
- const selectLineEnd = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).to));
23177
+ const selectLineEnd = view => extendSel(view, true, range => EditorSelection.cursor(view.lineBlockAt(range.head).to));
23129
23178
  /**
23130
23179
  Move the selection to the start of the document.
23131
23180
  */
@@ -0,0 +1,41 @@
1
+ import apiConstructor from '../api.js'
2
+
3
+ const api = apiConstructor()
4
+
5
+ // Create WebSocket connection.
6
+ const socket = new WebSocket('ws://localhost:8974')
7
+
8
+ // Connection opened
9
+ socket.addEventListener('open', () => {
10
+ console.log('Watching for Blueprint changes')
11
+ })
12
+
13
+ // Listen for messages
14
+ socket.addEventListener('message', (event) => {
15
+ if (event.data === 'reload-blueprint') {
16
+ api.blueprint()
17
+ return
18
+ }
19
+
20
+ let message
21
+ try {
22
+ message = JSON.parse(event.data)
23
+ } catch {
24
+ return
25
+ }
26
+
27
+ if (message?.type === 'nested-invoke') {
28
+ window.dispatchEvent(
29
+ new CustomEvent('nested-invoke', {
30
+ detail: {
31
+ name: message.name,
32
+ order: message.order,
33
+ logs: message.logs,
34
+ json: message.json,
35
+ error: message.error,
36
+ timings: message.timings,
37
+ },
38
+ }),
39
+ )
40
+ }
41
+ })
@@ -76,7 +76,34 @@ console.error = (...args) => {
76
76
  process.stdout.write(`${logPrefix('error', ...args)}\n`)
77
77
  }
78
78
 
79
- // Start when payload data arrives from parent process
79
+ /**
80
+ * Pending nested `context.invoke` calls, keyed by correlation id. Each entry
81
+ * holds the resolve/reject of the Promise returned to the user's handler; it is
82
+ * settled when the matching `invoke-response` arrives from the parent.
83
+ * @type {Map<string, {resolve: (value: unknown) => void, reject: (reason: Error) => void}>}
84
+ */
85
+ const pendingInvokes = new Map()
86
+ let invokeSeq = 0
87
+
88
+ /**
89
+ * `context.invoke(name, payload)` — ask the parent to execute another blueprint
90
+ * function and resolve with its return value. Sent over IPC and correlated by id.
91
+ * @param {string} name
92
+ * @param {Record<string, unknown>} [payload]
93
+ * @returns {Promise<unknown>}
94
+ */
95
+ function invokeFunction(name, payload = {}) {
96
+ if (typeof name !== 'string' || name.length === 0) {
97
+ return Promise.reject(new Error('context.invoke requires a function name'))
98
+ }
99
+ const id = String(++invokeSeq)
100
+ return new Promise((resolve, reject) => {
101
+ pendingInvokes.set(id, {resolve, reject})
102
+ process.send(JSON.stringify({type: 'invoke-request', id, name, payload}))
103
+ })
104
+ }
105
+
106
+ // Listen for IPC messages from the parent process.
80
107
  process.on('message', async (data) => {
81
108
  let jsonData = null
82
109
  try {
@@ -86,6 +113,25 @@ process.on('message', async (data) => {
86
113
  return
87
114
  }
88
115
 
116
+ // Response to a nested context.invoke() — route to the awaiting Promise and stop.
117
+ if (jsonData?.type === 'invoke-response') {
118
+ const pending = pendingInvokes.get(jsonData.id)
119
+ if (!pending) return
120
+ pendingInvokes.delete(jsonData.id)
121
+ if (jsonData.ok) {
122
+ pending.resolve(jsonData.result?.json)
123
+ } else {
124
+ const {message, stack, name} = jsonData.error ?? {}
125
+ const error = new Error(message ?? 'Nested invoke failed')
126
+ if (stack) error.stack = stack
127
+ if (name) error.name = name
128
+ pending.reject(error)
129
+ }
130
+ return
131
+ }
132
+
133
+ // Otherwise this is the initial start message. Support the legacy untyped
134
+ // `{srcPath, payload}` shape as well as the new `{type: 'start', ...}`.
89
135
  const {srcPath, payload} = jsonData
90
136
  const {context, ...event} = payload
91
137
 
@@ -93,17 +139,22 @@ process.on('message', async (data) => {
93
139
  let errorLogs = ''
94
140
  let json = null
95
141
 
96
- // Capture stdout and stderr
142
+ // Capture stdout and stderr in-memory for the terminal `result` message, and
143
+ // ALSO send them through to the real stdio pipes. The parent captures those
144
+ // pipes live, so logs written before a timeout (child killed) or a
145
+ // process.exit() aren't lost
97
146
  const originalStdoutWrite = process.stdout.write.bind(process.stdout)
98
147
  const originalStderrWrite = process.stderr.write.bind(process.stderr)
99
148
 
100
- process.stdout.write = (chunk) => {
149
+ process.stdout.write = (...args) => {
150
+ const chunk = args[0]
101
151
  if (typeof chunk === 'string') logs += chunk
102
- return true
152
+ return originalStdoutWrite(...args)
103
153
  }
104
- process.stderr.write = (chunk) => {
154
+ process.stderr.write = (...args) => {
155
+ const chunk = args[0]
105
156
  if (typeof chunk === 'string') errorLogs += chunk
106
- return true
157
+ return originalStderrWrite(...args)
107
158
  }
108
159
 
109
160
  try {
@@ -120,6 +171,9 @@ process.on('message', async (data) => {
120
171
  // Replace resources JSON with Resources API
121
172
  context.resources = createResourcesApi(context.resources)
122
173
 
174
+ // Allow the handler to invoke other blueprint functions by name.
175
+ context.invoke = invokeFunction
176
+
123
177
  json = await eventHandler({
124
178
  context,
125
179
  event,
@@ -132,13 +186,14 @@ process.on('message', async (data) => {
132
186
  process.stderr.write = originalStderrWrite
133
187
 
134
188
  // Send result to parent
135
- process.send(JSON.stringify({json, logs, errorLogs}))
189
+ process.send(JSON.stringify({type: 'result', json, logs, errorLogs}))
136
190
  } catch (err) {
137
191
  // Restore streams on error
138
192
  process.stdout.write = originalStdoutWrite
139
193
  process.stderr.write = originalStderrWrite
140
194
 
141
195
  const errorInfo = {
196
+ type: 'result',
142
197
  json: null,
143
198
  logs,
144
199
  errorLogs,
@@ -1,4 +1,4 @@
1
- import { type FunctionGroqResource, type FunctionResource, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from '../types.js';
1
+ import { type FunctionGroqResource, type FunctionResource, type InvocationResponse, type InvokeContextOptions, type InvokeExecutionOptions, type InvokeGroqPayloadOptions, type InvokePayloadOptions } from '../../types.js';
2
2
  export declare function sanitizeLogs(logs: string): string;
3
3
  export declare function applyGroqRule(resource: FunctionGroqResource, payload: InvokeGroqPayloadOptions, projectId: string | undefined, dataset: string | undefined): Promise<any>;
4
4
  export default function invoke(resource: FunctionResource, payload: InvokePayloadOptions, context: InvokeContextOptions, options: InvokeExecutionOptions): Promise<InvocationResponse>;