@bun-native/protocol 0.33.0 → 0.33.1

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/src/framing.ts CHANGED
@@ -1,5 +1,12 @@
1
- /** Maximum UTF-8 JSON payload bytes, excluding the newline delimiter. */
2
- export const MAX_FRAME_BYTES = 65_536
1
+ /**
2
+ * Maximum command frame (runtime to host) in UTF-8 JSON bytes, excluding the
3
+ * newline. One transaction is one batch, so this bounds a full-window mount.
4
+ */
5
+ export const MAX_FRAME_BYTES = 16_777_216
6
+ /** Maximum event frame (host to runtime): one text value or service result. */
7
+ export const MAX_EVENT_FRAME_BYTES = 65_536
8
+ /** Decoder capacity kept between frames; a larger frame borrows memory while it is read. */
9
+ const RETAINED_FRAME_BYTES = 65_536
3
10
 
4
11
  export class ProtocolError extends Error {
5
12
  override readonly name = 'ProtocolError'
@@ -24,17 +31,35 @@ export function encodeFrame(value: unknown): Uint8Array {
24
31
  }
25
32
 
26
33
  /**
27
- * Incremental, bounded JSON-lines decoder for a native process pipe.
34
+ * Incremental, bounded JSON-lines decoder for a native process pipe. `limit`
35
+ * is the largest frame payload it accepts: the runtime reads events with
36
+ * `MAX_EVENT_FRAME_BYTES`; a peer reading commands keeps the default.
28
37
  * A protocol error poisons the decoder; the caller must terminate that session.
29
38
  * Successful finish closes the decoder and is idempotent.
30
39
  */
31
40
  export class FrameDecoder {
32
- private readonly buffer = new Uint8Array(MAX_FRAME_BYTES)
41
+ private buffer = new Uint8Array(0)
33
42
  // Preserve a leading BOM so JSON.parse rejects it, matching Rust serde_json.
34
43
  private readonly utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true })
35
44
  private length = 0
36
45
  private state: 'open' | 'finished' | 'failed' = 'open'
37
46
 
47
+ constructor(private readonly limit: number = MAX_FRAME_BYTES) {}
48
+
49
+ /** Append a partial frame, growing the buffer geometrically up to the limit. */
50
+ private append(bytes: Uint8Array): void {
51
+ const length = this.length + bytes.length
52
+ if (length > this.buffer.length) {
53
+ const grown = new Uint8Array(
54
+ Math.min(this.limit, Math.max(length, 2 * this.buffer.length, 4_096))
55
+ )
56
+ grown.set(this.buffer.subarray(0, this.length))
57
+ this.buffer = grown
58
+ }
59
+ this.buffer.set(bytes, this.length)
60
+ this.length = length
61
+ }
62
+
38
63
  push(chunk: Uint8Array): unknown[] {
39
64
  if (this.state !== 'open')
40
65
  throw new ProtocolError(
@@ -49,8 +74,7 @@ export class FrameDecoder {
49
74
  const newline = chunk.indexOf(10, start)
50
75
  const end = newline === -1 ? chunk.length : newline
51
76
  const size = end - start
52
- if (this.length + size > MAX_FRAME_BYTES)
53
- throw new ProtocolError('Frame exceeds byte limit')
77
+ if (this.length + size > this.limit) throw new ProtocolError('Frame exceeds byte limit')
54
78
  if (newline !== -1 && this.length === 0) {
55
79
  // A frame wholly inside this chunk decodes in place, without a copy.
56
80
  if (size === 0) throw new ProtocolError('Empty frame')
@@ -58,12 +82,12 @@ export class FrameDecoder {
58
82
  start = newline + 1
59
83
  continue
60
84
  }
61
- this.buffer.set(chunk.subarray(start, end), this.length)
62
- this.length += size
85
+ this.append(chunk.subarray(start, end))
63
86
  if (newline === -1) break
64
87
  const json = this.utf8.decode(this.buffer.subarray(0, this.length))
65
- frames.push(JSON.parse(json) as unknown)
66
88
  this.length = 0
89
+ if (this.buffer.length > RETAINED_FRAME_BYTES) this.buffer = new Uint8Array(0)
90
+ frames.push(JSON.parse(json) as unknown)
67
91
  start = newline + 1
68
92
  }
69
93
  } catch (error) {
package/src/index.ts CHANGED
@@ -1,16 +1,16 @@
1
- export { encodeFrame, FrameDecoder, MAX_FRAME_BYTES, ProtocolError } from './framing.ts'
1
+ export {
2
+ encodeFrame,
3
+ FrameDecoder,
4
+ MAX_EVENT_FRAME_BYTES,
5
+ MAX_FRAME_BYTES,
6
+ ProtocolError,
7
+ } from './framing.ts'
2
8
  export {
3
9
  DECORATION_FIELDS,
4
- MAX_ACCESSIBLE_SET_SIZE,
5
10
  MAX_OVERLAY_DEPTH,
6
11
  parseElement,
7
12
  SPLIT_DIVIDER,
8
13
  type Element,
9
- type OverlayElement,
10
- type AccessibleLabel,
11
- type BoxAccessibility,
12
- type OverlayKeyboard,
13
- type SplitStyle,
14
14
  type Style,
15
15
  } from './elements.ts'
16
16
  export { MAX_TEXT_BYTES } from './values.ts'
@@ -26,12 +26,10 @@ export {
26
26
  parseFilePath,
27
27
  parseFilesystemRoots,
28
28
  parseRootId,
29
- type DirectoryEntry,
30
29
  type FileAccess,
31
30
  type FileKind,
32
31
  type FileOperation,
33
32
  type FileResult,
34
- type FilesystemRoot,
35
33
  type WriteMode,
36
34
  } from './files.ts'
37
35
  export {
@@ -42,14 +40,11 @@ export {
42
40
  MAX_PROCESSES,
43
41
  parseProcessExecutables,
44
42
  parseProcessLaunch,
45
- parseProcessLimits,
46
43
  parseProcessLimitsArgument,
47
44
  PROCESS_SIGNALS,
48
45
  processArguments,
49
46
  processLimitsArgument,
50
47
  processesFromArguments,
51
- type ProcessExecutable,
52
- type ProcessLimits,
53
48
  type ProcessExit,
54
49
  type ProcessLaunch,
55
50
  type ProcessSignal,
@@ -58,19 +53,12 @@ export {
58
53
  } from './processes.ts'
59
54
  export {
60
55
  APP_MENU_KEYS,
61
- APP_MENU_NAMED_KEYS,
62
- APP_MENU_PUNCTUATION_KEYS,
63
56
  MAX_APP_MENU_DEPTH,
64
57
  MAX_APP_MENU_ENTRIES,
65
- MAX_APP_MENU_ITEM_ID_BYTES,
66
- MAX_APP_MENU_LABEL_BYTES,
67
- MAX_APP_MENUS,
68
58
  parseAppMenus,
69
59
  parseContextMenuItems,
70
60
  type AppMenu,
71
61
  type AppMenuEntry,
72
- type MenuShortcut,
73
- type MenuShortcutModifiers,
74
62
  } from './app-menu.ts'
75
63
  export {
76
64
  FILE_DIALOG_MODES,
@@ -88,7 +76,6 @@ export {
88
76
  type FileDialogMode,
89
77
  type FileDialogOptions,
90
78
  type FileDialogResult,
91
- type FileFilter,
92
79
  } from './dialogs.ts'
93
80
  export {
94
81
  MAX_MESSAGE_DIALOG_BUTTONS,
@@ -97,8 +84,6 @@ export {
97
84
  MAX_MESSAGE_DIALOG_LABEL_BYTES,
98
85
  MAX_MESSAGE_DIALOG_MESSAGE_BYTES,
99
86
  MAX_MESSAGE_DIALOG_TITLE_BYTES,
100
- MAX_MESSAGE_DIALOGS,
101
- parseMessageDialogOptions,
102
87
  type MessageDialogButton,
103
88
  type MessageDialogOptions,
104
89
  type MessageDialogResult,
@@ -109,8 +94,6 @@ export {
109
94
  MAX_DROP_ITEMS,
110
95
  MAX_DROP_NAME_BYTES,
111
96
  MAX_DROP_TARGETS,
112
- type DragState,
113
- type DroppedItem,
114
97
  type DropRejection,
115
98
  } from './drops.ts'
116
99
  export {
@@ -138,7 +121,10 @@ export {
138
121
  PROTOCOL_VERSION,
139
122
  REQUIRED_CAPABILITIES,
140
123
  type Command,
124
+ type ApplicationEvent,
125
+ type HostEvent,
141
126
  type NativeEvent,
127
+ type WindowEvent,
142
128
  type Operation,
143
129
  type Limits,
144
130
  type ErrorCode,
@@ -1,8 +1,6 @@
1
1
  import { ProtocolError } from './framing.ts'
2
- import { keys, object, string } from './values.ts'
2
+ import { fields, keys, object, string } from './values.ts'
3
3
 
4
- /** Live OS message dialogs per session. Each window owns at most one at a time. */
5
- export const MAX_MESSAGE_DIALOGS = 16
6
4
  export const MAX_MESSAGE_DIALOG_BUTTONS = 4
7
5
  export const MAX_MESSAGE_DIALOG_TITLE_BYTES = 256
8
6
  export const MAX_MESSAGE_DIALOG_MESSAGE_BYTES = 2_048
@@ -39,9 +37,11 @@ function text(value: unknown, field: string, limit: number, block: boolean): str
39
37
  return result
40
38
  }
41
39
 
40
+ const BUTTON_KEYS = fields('id', 'label')
41
+
42
42
  function button(value: unknown): MessageDialogButton {
43
43
  const data = object(value)
44
- keys(data, ['id', 'label'])
44
+ keys(data, BUTTON_KEYS)
45
45
  return {
46
46
  id: text(data['id'], 'message dialog button id', MAX_MESSAGE_DIALOG_ID_BYTES, false),
47
47
  label: text(
@@ -53,17 +53,19 @@ function button(value: unknown): MessageDialogButton {
53
53
  }
54
54
  }
55
55
 
56
+ const MESSAGE_DIALOG_OPTIONS_KEYS = fields(
57
+ 'title',
58
+ 'message',
59
+ 'detail',
60
+ 'buttons',
61
+ 'default_button',
62
+ 'cancel_button',
63
+ 'dismissible'
64
+ )
65
+
56
66
  export function parseMessageDialogOptions(value: unknown): MessageDialogOptions {
57
67
  const data = object(value)
58
- keys(data, [
59
- 'title',
60
- 'message',
61
- 'detail',
62
- 'buttons',
63
- 'default_button',
64
- 'cancel_button',
65
- 'dismissible',
66
- ])
68
+ keys(data, MESSAGE_DIALOG_OPTIONS_KEYS)
67
69
  const list = data['buttons']
68
70
  if (!Array.isArray(list) || list.length < 1 || list.length > MAX_MESSAGE_DIALOG_BUTTONS)
69
71
  throw new ProtocolError('A message dialog needs between 1 and 4 buttons')
@@ -104,20 +106,24 @@ export function parseMessageDialogOptions(value: unknown): MessageDialogOptions
104
106
  return options
105
107
  }
106
108
 
109
+ const MESSAGE_DIALOG_RESULT_BUTTON_KEYS = fields('kind', 'id')
110
+ const MESSAGE_DIALOG_RESULT_DISMISSED_KEYS = fields('kind')
111
+ const MESSAGE_DIALOG_RESULT_FAILED_KEYS = fields('kind')
112
+
107
113
  export function parseMessageDialogResult(value: unknown): MessageDialogResult {
108
114
  const data = object(value)
109
115
  switch (data['kind']) {
110
116
  case 'button':
111
- keys(data, ['kind', 'id'])
117
+ keys(data, MESSAGE_DIALOG_RESULT_BUTTON_KEYS)
112
118
  return {
113
119
  kind: 'button',
114
120
  id: text(data['id'], 'message dialog result id', MAX_MESSAGE_DIALOG_ID_BYTES, false),
115
121
  }
116
122
  case 'dismissed':
117
- keys(data, ['kind'])
123
+ keys(data, MESSAGE_DIALOG_RESULT_DISMISSED_KEYS)
118
124
  return { kind: 'dismissed' }
119
125
  case 'failed':
120
- keys(data, ['kind'])
126
+ keys(data, MESSAGE_DIALOG_RESULT_FAILED_KEYS)
121
127
  return { kind: 'failed' }
122
128
  default:
123
129
  throw new ProtocolError('Unknown message dialog result')