io-profile.ts (3324B)
1 // This table is the implemented browser provider, not the native catalogue. 2 import { canonicalNat, decodeAction, ProtocolError, type Reader } from './io-protocol.js'; 3 import type { Wire } from './io-core.js'; 4 export const coreNames = ['pure', 'bind', 'fail', 'scope', 'current-scope', 'spawn', 'join', 'discard', 'cancel', 'cancelled'] as const; 5 const groups: Readonly<Record<string, readonly string[]>> = Object.freeze({ 6 runtime: ['capabilities', 'inspect'], standard: ['input', 'output', 'error'], 7 stream: ['read', 'write'], resource: ['close'], utf8: ['encode', 'decode'], 8 }); 9 export function available(group: string, revision: number, operation: string): boolean { 10 return revision === 1 && Object.hasOwn(groups, group) && groups[group]!.includes(operation); 11 } 12 export function capabilities<H>(wire: Wire<H>, backend: string): H { 13 const { tree } = wire, text = (s: string) => tree.text(s), number = (n: number) => tree.natural(BigInt(n)); 14 const strings = (xs: readonly string[]) => wire.list(xs.map(text)), row = (name: string, value: H) => wire.list([text(name), value]); 15 return wire.list([ 16 row('schema', number(1)), row('io-version', number(2)), row('backend', text(backend)), row('authority', text('browser-terminal')), 17 row('core', strings(coreNames)), row('groups', wire.list(Object.entries(groups).map(([name, ops]) => wire.list([text(name), number(1), strings(ops)])))), 18 row('limits', wire.list(Object.entries({ 'io-actions': 65536, 'chunk-bytes': 4096, 'transfer-handles': 256, 'utf8-encode-scalars': 1024, 'input-line-scalars': 256, 'output-attempted-bytes': 16384 }).map(([key, value]) => row(key, number(value))))), 19 row('restrictions', strings(['action-boundary-cancellation', 'no-hard-deadlines', 'borrowed-standard-streams', 'standard-stream-leases-per-run', 'utf8-strict-scalars', 'text-line-input', 'byte-preserving-output', 'no-filesystem', 'no-network', 'no-processes'])), 20 ]); 21 } 22 export function inspect<H>(wire: Wire<H>, value: H): H { 23 const action = decodeAction(wire.tree, value), t = wire.tree; 24 return action.kind === 'core' ? wire.list([t.text('core'), t.text(coreNames[action.code]!)]) : 25 wire.list([t.text('facility'), t.text(action.group), t.natural(BigInt(action.revision)), t.text(action.operation), t.text(available(action.group, action.revision, action.operation) ? 'available' : 'unsupported')]); 26 } 27 // Authenticate framing before selecting an interpreter. No action is observed. 28 export function envelope<H>(tree: Reader<H>, value: H): { version: 2; action: H } { 29 const root = tree.observe(value).node; 30 if (root.kind !== 'fork') throw new ProtocolError('invalid IO envelope'); 31 let rest = root.left; 32 for (const char of 'tricuIO') { 33 const node = tree.observe(rest).node; 34 if (node.kind !== 'fork' || canonicalNat(tree, node.left, 7) !== BigInt(char.charCodeAt(0))) throw new ProtocolError('Missing tricuIO sentinel'); 35 rest = node.right; 36 } 37 if (tree.observe(rest).node.kind !== 'leaf') throw new ProtocolError('Missing tricuIO sentinel'); 38 const body = tree.observe(root.right).node; 39 if (body.kind !== 'fork') throw new ProtocolError('invalid IO envelope'); 40 const version = canonicalNat(tree, body.left, 2); 41 if (version !== 2n) throw new ProtocolError('Unsupported tricuIO version'); 42 return { version: 2, action: body.right }; 43 }