docs(runtime): clarify streaming import cursors and byte reads
This commit is contained in:
@@ -9,7 +9,10 @@ import type {ImportSource,ImportLine} from './imports.ts';
|
||||
import {fault,validJson} from '../canvas-core/values.ts';
|
||||
/** Seekable JSON array reader. Cursors are byte offsets after '[' or a record comma.
|
||||
* Structure errors end the source; previously published records remain explicit. */
|
||||
/** 捕获Blob/指纹与读取预算,缓存一块源字节;本解析器不分配业务ID、不发布history或发送请求。 */
|
||||
/** 捕获 Blob/max/chunk 与返回指纹标量,按字节 offset 每次返回一条记录,缓存一个读取块。
|
||||
* readBytes 与调度选项仍从原 options 读取,调用方应保持它们稳定;这里不建立源摘要/读取证明。
|
||||
* 默认单条 256KiB/块 16KiB,未给 maxRecordBytes 设置总上限,也不限制源文件总长。
|
||||
* 不分配业务 ID、不发布 history 或发送请求;缓存不是持久进度,合法边界由 runner 保存。 */
|
||||
export function createJsonArrayImportSource(options:{bytes:Blob;fingerprint:string;readBytes?:ImportByteReader;maxRecordBytes?:number;chunkBytes?:number}&ImportScheduling):ImportSource{
|
||||
const bytes=options.bytes,max=options.maxRecordBytes??256*1024,chunk=options.chunkBytes??16384;
|
||||
if(!(bytes instanceof Blob)||!options.fingerprint||!Number.isSafeInteger(max)||max<1||!Number.isSafeInteger(chunk)||chunk<1||chunk>max)throw fault('invalid_import_source');
|
||||
@@ -18,6 +21,7 @@ export function createJsonArrayImportSource(options:{bytes:Blob;fingerprint:stri
|
||||
/** read每次返回一个ImportLine及nextCursor;EOF返回null,源级I/O失败仍抛出而不伪装坏记录。 */
|
||||
return Object.freeze({fingerprint:options.fingerprint,parserVersion:3,async read(start:number,signal:AbortSignal):Promise<ImportLine|null>{
|
||||
if(!Number.isSafeInteger(start)||start<0||start>bytes.size)throw fault('invalid_import_cursor');
|
||||
// EOF offset 直接返回 null,不重扫先前数组结构;任意范围内 seek 也不证明是合法记录起点。
|
||||
if(start>0&&start===bytes.size){if(signal.aborted)throw fault('import_aborted');return null;}
|
||||
if(bytes.size===0)throw fault('invalid_json_array');
|
||||
let cursor=start;const budget=createImportBudget(signal,options);
|
||||
@@ -32,7 +36,8 @@ export function createJsonArrayImportSource(options:{bytes:Blob;fingerprint:stri
|
||||
await spaces();if(start===0){if(await peek()!==91)return failure('invalid_json_array');await advance();await spaces();}
|
||||
if(await peek()===93){await advance();await spaces();if(cursor!==bytes.size)return failure('invalid_json_array');if(start!==0){/* Empty tail is valid only after a record, not a trailing comma. */return failure('invalid_json_array');}return null;}
|
||||
if(await peek()===-1)return failure('incomplete_json_array');
|
||||
// quoted/escaped隔离字符串分隔符,stack配对{}与[];超大小后仍扫描至合法边界但不再保留字节。
|
||||
// parts 是至多 max 个字节数值的普通数组(内存成本不等于 max 字节),stack 至多保留 256 层括号。
|
||||
// quoted/escaped 只定位边界,不完整校验 JSON;超大小后仍扫描但不再收集,坏结构仍可能终结整个源。
|
||||
const recordStart=cursor,parts:number[]=[],stack:number[]=[];let quoted=false,escaped=false,oversized=false;
|
||||
while(true){const byte=await peek();if(byte===-1)return failure('incomplete_json_array');
|
||||
if(!quoted&&stack.length===0&&(byte===44||byte===93))break;
|
||||
@@ -41,13 +46,15 @@ export function createJsonArrayImportSource(options:{bytes:Blob;fingerprint:stri
|
||||
else if(byte===34)quoted=true;else if(byte===123||byte===91){stack.push(byte);if(stack.length>256)return failure('json_nesting_limit');}else if(byte===125||byte===93){if(stack.pop()!==(byte===125?123:91))return failure('invalid_json_array');}
|
||||
await advance();
|
||||
}
|
||||
// 消费外层逗号或]并检查尾部/尾逗号;内嵌数组的]已由stack处理,不会截断记录。
|
||||
// 先确认记录后的外层分隔与尾部,再返回此记录;若尾逗号/垃圾在此处发现,本记录也返回结构失败。
|
||||
// 数组内嵌 ] 已由 stack 处理;nextCursor 消费分隔和后续空白,最后一项则推进到真实 EOF。
|
||||
const delimiter=await peek();await advance();
|
||||
if(delimiter===93){await spaces();if(cursor!==bytes.size)return failure('invalid_json_array');}
|
||||
else {await spaces();if(await peek()===93||await peek()===-1)return failure('invalid_json_array');}
|
||||
const base={cursor:start,nextCursor:cursor,key:`byte:${start}`};
|
||||
if(oversized)return{...base,error:'record_too_large'};
|
||||
// 只解码这一记录:fatal UTF8防替换字符掩盖损坏,再JSON.parse/validJson校验值域。
|
||||
// fatal UTF-8 与 JSON.parse 在边界已确定后处理,失败可随 nextCursor 继续下一项;不做元素业务 schema 校验。
|
||||
// JSON.parse 同步完成且无中途取消,记录扫描的预算不抢占解码/解析;调用方仍需在采纳结果前校验生命周期。
|
||||
let text:string;try{text=new TextDecoder('utf-8',{fatal:true}).decode(new Uint8Array(parts));}catch{return{...base,error:'invalid_utf8'};}
|
||||
try{const value=JSON.parse(text);return validJson(value)?{...base,value}:{...base,error:'invalid_json_value'};}catch{return{...base,error:'invalid_json'};}
|
||||
}});
|
||||
|
||||
Reference in New Issue
Block a user