@agile-team/mach-report 2.9.1 → 2.11.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.
- package/README.md +11 -7
- package/dist/ExcelBindingWizard-CjIiVox-.cjs +1 -0
- package/dist/ExcelBindingWizard-Dxm-K5HL.js +1240 -0
- package/dist/ParamEditor-D-Hf5xfO.cjs +1 -0
- package/dist/ParamEditor-DRGxzxeO.js +381 -0
- package/dist/ReportBatchPanel-BYgg0EDB.cjs +1 -0
- package/dist/ReportBatchPanel-Bby5yLzU.js +287 -0
- package/dist/ReportPreview-DWXROHBP.cjs +6 -0
- package/dist/ReportPreview-DiNXhQI0.js +4138 -0
- package/dist/ReportTemplateLibrary-C6CO7YCT.cjs +1 -0
- package/dist/ReportTemplateLibrary-DfzDXRvr.js +260 -0
- package/dist/RuleEditor-C2QsOHOs.cjs +1 -0
- package/dist/RuleEditor-D9wQHrkf.js +2024 -0
- package/dist/TemplateReviewPanel-BKofGrV9.js +222 -0
- package/dist/TemplateReviewPanel-DwtOcLue.cjs +1 -0
- package/dist/analytics.cjs +1 -1
- package/dist/analytics.d.cts +4 -0
- package/dist/analytics.d.ts +4 -0
- package/dist/analytics.js +40 -39
- package/dist/{assets-ahTRUXRk.cjs → assets-Bu6UohJx.cjs} +1 -1
- package/dist/{assets-DDsfEi8R.js → assets-DegqDNqY.js} +1 -1
- package/dist/batch-Bt3Fd27Z.cjs +1 -0
- package/dist/batch-mRqhhhjt.js +364 -0
- package/dist/{capabilities-Cm9hZ3uV.js → capabilities-CK_7hizc.js} +16 -16
- package/dist/capabilities-CsZ-pNHR.cjs +1 -0
- package/dist/computed-Bvk0W15F.js +521 -0
- package/dist/computed-CGV2a809.cjs +1 -0
- package/dist/config-B612mw0x.js +525 -0
- package/dist/config-BRoLo2oj.cjs +1 -0
- package/dist/designer.cjs +2 -2
- package/dist/designer.d.cts +695 -50
- package/dist/designer.d.ts +695 -50
- package/dist/designer.js +2213 -1748
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +205 -6
- package/dist/index.d.ts +205 -6
- package/dist/index.js +808 -1033
- package/dist/injection-keys-Db_DyLkm.cjs +1 -0
- package/dist/injection-keys-e85udZCp.js +7 -0
- package/dist/list-cell-BuYn4tGF.cjs +1 -0
- package/dist/list-cell-ClFra1zS.js +173 -0
- package/dist/matrix-DVyWiYsd.cjs +1 -0
- package/dist/matrix-yZsGnldA.js +227 -0
- package/dist/param-validation-BlHHXs6u.js +153 -0
- package/dist/param-validation-DCK039V1.cjs +1 -0
- package/dist/pdf.cjs +49 -49
- package/dist/pdf.d.cts +31 -1
- package/dist/pdf.d.ts +31 -1
- package/dist/pdf.js +4797 -4650
- package/dist/plugin-BDTU4D94.cjs +1 -0
- package/dist/plugin-Cd5G2QQn.js +22 -0
- package/dist/region-binding-workflow-CXDmt9Bs.js +977 -0
- package/dist/region-binding-workflow-DhQvu1co.cjs +3 -0
- package/dist/{report-params-B_VCihAh.js → report-params-B44imUd4.js} +1 -1
- package/dist/{report-params-CjWrjPl6.cjs → report-params-SJfhKgaX.cjs} +1 -1
- package/dist/server/node.d.cts +48 -5
- package/dist/server/node.d.ts +48 -5
- package/dist/server-node.cjs +1 -1
- package/dist/server-node.js +503 -317
- package/dist/server.cjs +1 -1
- package/dist/server.d.cts +252 -4
- package/dist/server.d.ts +252 -4
- package/dist/server.js +567 -469
- package/dist/style.css +1 -1
- package/dist/template-registry-WNXdkiET.js +506 -0
- package/dist/template-registry-ukBGlm8N.cjs +1 -0
- package/dist/template-review-BXp2oyqg.cjs +1 -0
- package/dist/template-review-BdcXSYbx.js +1616 -0
- package/dist/tree-BX380Rr7.cjs +1 -0
- package/dist/tree-CqcQ99NM.js +172 -0
- package/dist/useLocalDraft-C07SYRnC.js +11 -0
- package/dist/useLocalDraft-Yiw1wFgI.cjs +1 -0
- package/dist/{validate-DWPF7icl.js → validate-7s0yn6ii.js} +26 -26
- package/dist/validate-DsRiNw8h.cjs +1 -0
- package/dist/vue/async.d.cts +118 -6
- package/dist/vue/async.d.ts +118 -6
- package/dist/vue/index.d.cts +177 -11
- package/dist/vue/index.d.ts +177 -11
- package/dist/vue-async.cjs +1 -1
- package/dist/vue-async.js +5 -5
- package/dist/vue.cjs +1 -1
- package/dist/vue.js +116 -113
- package/dist/{import-template-CNMLJSQw.js → workbook-session-BKOTwcoq.js} +4290 -4227
- package/dist/{import-template-CpxunKVp.cjs → workbook-session-CLYLhZWu.cjs} +17 -17
- package/dist/worker.cjs +1 -1
- package/dist/worker.d.cts +9 -2
- package/dist/worker.d.ts +9 -2
- package/dist/worker.js +1 -1
- package/dist/xlsx.cjs +4 -2
- package/dist/xlsx.d.cts +159 -3
- package/dist/xlsx.d.ts +159 -3
- package/dist/xlsx.js +354 -192
- package/package.json +1 -1
- package/dist/ExcelBindingWizard-DiaxvWpQ.cjs +0 -1
- package/dist/ExcelBindingWizard-RLJIXKKG.js +0 -816
- package/dist/ReportPreview-B5ZNEasW.js +0 -4439
- package/dist/ReportPreview-DRtVPUYo.cjs +0 -6
- package/dist/RuleEditor-CskN5Fr3.cjs +0 -1
- package/dist/RuleEditor-CtJFzKuY.js +0 -1578
- package/dist/TemplateReviewPanel-C_Lmzis6.cjs +0 -1
- package/dist/TemplateReviewPanel-Cn32OY4_.js +0 -173
- package/dist/binding-preview-B5834rml.cjs +0 -2
- package/dist/binding-preview-ChXjN5L5.js +0 -649
- package/dist/capabilities-DYzWL7KS.cjs +0 -1
- package/dist/matrix-CNPDJxED.js +0 -745
- package/dist/matrix-Div9Ilh3.cjs +0 -1
- package/dist/param-validation-BWNjLcN0.js +0 -116
- package/dist/param-validation-CiTxFeZ8.cjs +0 -1
- package/dist/plugin-BWwIgZXV.js +0 -25
- package/dist/plugin-Dg-J1Rbm.cjs +0 -1
- package/dist/template-registry-CHMYZ7SD.cjs +0 -1
- package/dist/template-registry-R61AMCr0.js +0 -433
- package/dist/template-review-hvWWQFsi.js +0 -1027
- package/dist/template-review-jEcOfmGr.cjs +0 -1
- package/dist/validate-BDiMqkJC.cjs +0 -1
package/dist/server.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var re=Object.create;var z=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var ie=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of se(t))!oe.call(e,s)&&s!==r&&z(e,s,{get:()=>t[s],enumerable:!(n=ne(t,s))||n.enumerable});return e};var V=(e,t,r)=>(r=e!=null?re(ae(e)):{},ie(t||!e||!e.__esModule?z(r,"default",{value:e,enumerable:!0}):r,e));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("./template-review-jEcOfmGr.cjs"),M=require("./report-params-CjWrjPl6.cjs"),$=require("./param-validation-CiTxFeZ8.cjs"),ce=require("./validate-BDiMqkJC.cjs"),ue=require("./dataset-BkRmwgeJ.cjs"),A=require("./task-events-DQ276fn2.cjs"),le=require("./template-registry-CHMYZ7SD.cjs");class v extends Error{constructor(t,r,n){super(r),this.code=t,this.path=n,this.name="ReportRuntimeError"}}function x(e){if(!e?.aborted)return;if(e.reason instanceof v)throw e.reason;const t=new Error("报表执行已取消");throw t.name="AbortError",Object.defineProperty(t,"cause",{value:e.reason}),t}function S(e,t,r){if(!Number.isFinite(r)||r<0)throw new v("budget",`${t} 的执行统计无效`,`$.budget.${t}`);const n=e[t];if(n!==void 0&&r>n)throw new v("budget",`${t} 超出预算 ${n}(实际 ${r})`,`$.budget.${t}`)}function de(e){for(const[t,r]of Object.entries(e)){if(r!==void 0&&(!Number.isFinite(r)||r<=0))throw new v("budget",`${t} 应为正有限数字`,`$.budget.${t}`);if(t!=="maxDurationMs"&&r!==void 0&&!Number.isSafeInteger(r))throw new v("budget",`${t} 应为正安全整数`,`$.budget.${t}`);if(t==="maxDurationMs"&&r!==void 0&&r>2147483647)throw new v("budget","maxDurationMs 不能超过 2147483647",`$.budget.${t}`)}}async function L(e,t,r){const n=new AbortController,s=()=>n.abort(e?.reason);e?.addEventListener("abort",s,{once:!0}),e?.aborted&&s();const a=Date.now(),c=t===void 0?void 0:setTimeout(()=>n.abort(new v("timeout",`报表执行超过 ${t}ms`)),t);let p;try{x(n.signal);const w=new Promise((u,i)=>{p=()=>{try{x(n.signal)}catch(f){i(f)}},n.signal.addEventListener("abort",p,{once:!0})}),o=await Promise.race([r(n.signal),w]);if(x(n.signal),t!==void 0&&Date.now()-a>t)throw new v("timeout",`报表执行超过 ${t}ms`);return o}finally{c!==void 0&&clearTimeout(c),p&&n.signal.removeEventListener("abort",p),e?.removeEventListener("abort",s)}}function H(e,t){if(t.length===0)throw new P.ReportSourceError("config","缺少报表 ID");return t.map(r=>{if(!Object.prototype.hasOwnProperty.call(e,r))throw new P.ReportSourceError("config",`未注册报表 ${r}`);return e[r]})}function C(e){return e.grid?.cells.reduce((t,r)=>t+r.reduce((n,s)=>n+(s?1+(s.children?.reduce((a,c)=>a+C(c),0)??0):0),0),0)??0}function fe(e,t,r){return e.pages.reduce((n,s)=>n+s.components.reduce((a,c)=>c.kind!=="list"?a+C(c):a+P.estimateListLayoutCells(c,t,r),0),0)}function pe(e,t=Object.keys(e)){return H(e,t).map((r,n)=>{const s=t[n],a=M.getReportParamDefs(e,[s]);return{id:s,params:JSON.parse(JSON.stringify(a)),paramsSchema:$.reportParamsJsonSchema(a),datasetsSchema:Object.fromEntries(Object.entries(r.dataSchema??{}).map(([c,p])=>[c,ue.datasetJsonSchema(p)])),capabilities:{save:!!r.provider?.save,export:!!r.provider?.export,templates:!!r.provider?.templates}}})}function K(e,t){H(e,t.reportIds);const r=$.validateReportParams(M.getReportParamDefs(e,t.reportIds),t.params),n=t.reportIds.flatMap(a=>$.validateReportParams(M.getReportParamDefs(e,[a]),t.params).issues),s=new Map(n.map(a=>[JSON.stringify(a),a]));return{...r,ok:s.size===0,issues:[...s.values()]}}function ge(e,t){const r=ce.validateRenderPlan(e.plan);if(!r.ok)throw new P.ReportSourceError("validate",`执行器返回无效计划:${r.errors[0]?.path}`);const n=e.plan.pages.length,s=e.plan.pages.reduce((a,c)=>a+c.components.reduce((p,w)=>p+C(w),0),0);return S(t,"maxRows",e.stats.rows),S(t,"maxPages",n),S(t,"maxCells",s),{...e,stats:{...e.stats,pages:n,cells:s}}}function Q(e,t){const r=K(e.reports,t);if(!r.ok)throw new $.ReportParamValidationError(r.issues)}function X(e=[]){return e.map(({code:t,message:r,severity:n,pageIndex:s,componentIndex:a})=>({code:t,message:r,severity:n,path:`$.pages.${s}.components.${a}`}))}function U(e){const t={maxRows:1e5,maxPages:1e4,maxCells:2e6,maxOutputBytes:1e8,maxDurationMs:12e4,...e.budget};de(t);const r=async(s,a)=>{const c=Date.now();let p=0,w=0;const o=[],u={},i=Object.fromEntries(Object.entries(e.reports).map(([d,l])=>[d,{...l,pagination:{...e.pagination,...l.pagination,...a?{measurer:a}:{}},dataValidation:{...l.dataValidation,maxRows:Math.min(t.maxRows??1e5,l.dataValidation?.maxRows??1/0),maxCells:Math.min(t.maxCells??2e6,l.dataValidation?.maxCells??1/0)}}])),m=await P.createReportSource(i,{maxPages:t.maxPages,maxCells:t.maxCells,onDatasets:(d,l,y)=>{p+=Object.values(l).reduce((R,T)=>R+T.length,0),S(t,"maxRows",p),w+=fe(y,l,i[d]?.pagination),S(t,"maxCells",w)}})({...s,params:s.params??{},onWarning:d=>o.push({code:"layout",message:d,severity:"warning"}),onQueryResult:(d,l)=>{Object.defineProperty(u,d,{value:{revision:l.revision,sourceToken:l.sourceToken},enumerable:!0,writable:!0}),o.push(...l.diagnostics??[])}}).catch(d=>{throw d instanceof P.ReportSourceError&&d.cause instanceof P.PaginatePageLimitError?new v("budget",d.cause.message,"$.budget.maxPages"):d instanceof P.ReportSourceError&&d.cause instanceof P.PaginateCellLimitError?new v("budget",d.cause.message,"$.budget.maxCells"):d});x(s.signal);const h=m.pages.reduce((d,l)=>d+l.components.reduce((y,R)=>y+C(R),0),0);return S(t,"maxPages",m.pages.length),S(t,"maxCells",h),{plan:m,diagnostics:o,revisions:u,stats:{rows:p,pages:m.pages.length,cells:h,durationMs:Date.now()-c}}},n=async s=>{const a=Date.now();if(s.format!=="pdf"&&s.format!=="xlsx")throw new P.ReportSourceError("config","不支持的报表导出格式");const c=s.format==="pdf"?await import("@agile-team/mach-report/pdf"):void 0;x(s.signal);const p=s.pdf?.customFontFaces??(s.pdf?.customFontBytes?{regular:s.pdf.customFontBytes}:void 0),w=c&&p?c.createFontBytesMeasurer(p):void 0,o=await r(s,w);x(s.signal);const u=A.createReportTaskReporter(s),i=await u.run("encode",async()=>{let h;if(s.format==="pdf"){x(s.signal);const d=await c.renderPlanToPdf(o.plan,{...s.pdf,...w?{measurer:w}:{},fidelity:s.fidelity,signal:s.signal,onProgress:(l,y)=>u.progress("encode",l,y)});h=d.bytes,o.diagnostics.push(...X(d.diagnostics)),o.diagnostics.push(...d.fidelityWarnings.map(l=>({code:"pdf-fidelity",message:l,severity:"warning"})),...d.pageErrors.map(l=>({code:"pdf-page",message:l,severity:"error"}))),d.unsupportedTextCount&&o.diagnostics.push({code:"pdf-font",message:`${d.unsupportedTextCount} 段文本包含当前字体不支持的字符`,severity:"warning"})}else{const{renderPlanToXlsx:d}=await import("@agile-team/mach-report/xlsx");x(s.signal);const l=await d(o.plan,{...s.xlsx,fidelity:s.fidelity,signal:s.signal,onProgress:(y,R)=>u.progress("encode",y,R)});h=new Uint8Array(l.buffer),o.diagnostics.push(...X(l.diagnostics)),o.diagnostics.push(...l.warnings.map(y=>({code:"xlsx-fidelity",message:y,severity:"warning"})))}return x(s.signal),h});S(t,"maxOutputBytes",i.byteLength);const{plan:f,...m}=o;return{...m,bytes:i,format:s.format,contentType:s.format==="pdf"?"application/pdf":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",stats:{...m.stats,durationMs:Date.now()-a,outputBytes:i.byteLength}}};return{describe:s=>pe(e.reports,s),validate:s=>K(e.reports,s),prepare:s=>L(s.signal,t.maxDurationMs,async a=>{const{signal:c,onEvent:p,...w}=s,o={...w,taskId:w.taskId??A.reportTaskId()},u=p?m=>A.notifyReportTaskEvent(p,m,a):void 0,i={...o,signal:a,onEvent:u};A.createReportTaskReporter(i).runSync("validate",()=>Q(e,o));const f=e.executor?await e.executor.prepare(o,a,u):await r(i);return ge(f,t)}),export:s=>L(s.signal,t.maxDurationMs,async a=>{const{signal:c,onEvent:p,...w}=s,o={...w,taskId:w.taskId??A.reportTaskId()},u=p?m=>A.notifyReportTaskEvent(p,m,a):void 0,i={...o,signal:a,onEvent:u};if(A.createReportTaskReporter(i).runSync("validate",()=>Q(e,o)),o.format!=="pdf"&&o.format!=="xlsx")throw new P.ReportSourceError("config","不支持的报表导出格式");const f=e.executor?await e.executor.export(o,a,u):await n(i);return S(t,"maxRows",f.stats.rows),S(t,"maxPages",f.stats.pages),S(t,"maxCells",f.stats.cells),S(t,"maxOutputBytes",f.bytes.byteLength),{...f,stats:{...f.stats,outputBytes:f.bytes.byteLength}}})}}function we(e={}){const t=e.concurrency??1,r=e.maxQueued??20;if(!Number.isSafeInteger(t)||t<1||!Number.isSafeInteger(r)||r<0)throw new v("budget","队列并发应为正整数,排队上限应为非负整数");const n=[],s=new Set,a=new Set;let c=!1,p=0;const w=()=>{for(;!c&&s.size<t&&n.length;)n.shift().start()};return{get active(){return s.size},get pending(){return n.length},enqueue:(u,i={})=>{if(c)throw new v("closed","任务队列已关闭");if(x(i.signal),s.size>=t&&n.length>=r)throw new v("queue-full","报表任务队列已满");const f=i.id??`report-${++p}`;if(a.has(f))throw new v("queue-full","任务 ID 正在执行或排队");const m=new AbortController;let h="queued",d,l,y,R;const T=new Promise((b,E)=>{y=b,R=E}),F=()=>{i.signal?.removeEventListener("abort",B),a.delete(f)},N=b=>{if(h!=="queued"&&h!=="running")return;const E=h==="queued";h="cancelled",m.abort(b);try{x(m.signal)}catch(I){R(I)}if(E){const I=n.indexOf(q);I>=0&&n.splice(I,1),F(),w()}},B=()=>N(i.signal?.reason),_=(b,E)=>{h==="running"&&(!Number.isFinite(b)||b<0||E!==void 0&&(!Number.isFinite(E)||E<b)||(d={completed:b,...E===void 0?{}:{total:E}}))},te=b=>{h!=="running"||m.signal.aborted||(l={...b,taskId:f},b.completed!==void 0&&_(b.completed,b.total),A.notifyReportTaskEvent(i.onEvent,l,m.signal))},q={cancel:N,start:()=>{h==="queued"&&(h="running",s.add(q),Promise.resolve().then(()=>(x(m.signal),u({taskId:f,signal:m.signal,reportProgress:_,onEvent:te}))).then(b=>{h==="running"&&(h="completed",y(b))},b=>{h==="running"&&(h="failed",R(b))}).finally(()=>{s.delete(q),F(),w()}))}};return a.add(f),i.signal?.addEventListener("abort",B,{once:!0}),i.signal?.aborted?N(i.signal.reason):s.size<t?q.start():n.push(q),{id:f,get status(){return h},get progress(){return d},get event(){return l},result:T,cancel:N}},close:(u={})=>{c=!0;for(const i of[...n])i.cancel(new v("closed","任务队列已关闭"));if(u.cancelRunning)for(const i of s)i.cancel(new v("closed","任务队列已关闭"))}}}class g extends Error{constructor(t,r){super(r),this.code=t,this.name="ReportJobError"}}function J(e,t){if(typeof e!="string"||!e.trim()||e.length>256||typeof t!="string"||!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(t))throw new g("input","任务范围应非空,ID应为1–128位安全标识")}function me(e){const t=Array.isArray(e),r=Object.getPrototypeOf(e),n=Object.keys(e);if(r!==(t?Array.prototype:Object.prototype)&&!(r===null&&!t))throw new g("input","任务对象应为普通 JSON 对象");if(Object.getOwnPropertySymbols(e).length)throw new g("input","任务不能包含Symbol");if(Object.getOwnPropertyNames(e).filter(a=>!(t&&a==="length")).length!==n.length)throw new g("input","任务不能包含不可枚举属性");if(t&&(n.length!==e.length||n.some(a=>!/^(0|[1-9]\d*)$/.test(a)||Number(a)>=e.length)))throw new g("input","任务数组不能稀疏或包含额外属性");return n.sort()}function he(e,t){const r=Object.getOwnPropertyDescriptor(e,t);if(["__proto__","prototype","constructor"].includes(t)||r.get||r.set)throw new g("input","任务不能包含危险键或访问器");return r.value}function O(e,t={}){const r=t.maxNodes??2e4,n=t.maxCharacters??1e6;if(!Number.isSafeInteger(r)||r<1||r>2e4||!Number.isSafeInteger(n)||n<1||n>1e6)throw new g("input","任务JSON预算无效");let s=0,a=0;const c=new Set;function p(o,u){if(++s>r||u>32)throw new g("input","任务数据超过节点或深度预算");if(typeof o=="string"){if(a+=o.length,a>n)throw new g("input","任务文本超过预算");return o}if(o===null||typeof o=="boolean"||typeof o=="number"&&Number.isFinite(o))return o;if(!o||typeof o!="object"||c.has(o))throw new g("input","任务只允许无循环的有限 JSON 值");const i=Array.isArray(o),f=me(o);c.add(o);const m=Object.create(null);for(const h of f){if(a+=h.length,a>n)throw new g("input","任务文本超过预算");m[h]=p(he(o,h),u+1)}return c.delete(o),i?Array.from({length:o.length},(h,d)=>m[String(d)]):m}const w=JSON.stringify(p(e,0));if(w.length>n)throw new g("input","任务序列化数据超过字符预算");return w}function k(e){return JSON.parse(JSON.stringify(e))}function G(e,t,r,n){if(J(t,r),e.scope!==t||e.id!==r||e.revision!==(n??0)+1||!Number.isSafeInteger(e.revision))throw new g("input","任务身份或修订不匹配");if(![e.createdAt,e.updatedAt,e.attempt].every(s=>Number.isSafeInteger(s)&&s>=0)||!["queued","running","completed","failed","cancelled"].includes(e.status))throw new g("input","任务状态无效");if(e.lease&&(!e.lease.owner||!Number.isSafeInteger(e.lease.expiresAt)))throw new g("input","任务租约无效");if(!e.request||typeof e.request.kind!="string"||!e.request.kind.trim()||e.request.kind.length>128)throw new g("input","任务类型无效");O(e.request.payload,{maxNodes:19e3,maxCharacters:75e4}),O(e)}function Y(e,t){if(e&&(e.createdAt!==t.createdAt||O(e.request)!==O(t.request)))throw new g("input","任务创建时间与请求不可变更,请使用新任务ID")}function ee(e,t){return e.status==="queued"||e.status==="running"&&(e.lease?.expiresAt??0)<=t}function ye(){const e=new Map,t=(r,n)=>JSON.stringify([r,n]);return{read:async(r,n)=>{const s=e.get(t(r,n));return s?k(s):void 0},candidates:async(r,n)=>Array.from(e.values()).filter(s=>ee(s,r)).sort((s,a)=>s.createdAt-a.createdAt).slice(0,n).map(k),compareAndSwap:async(r,n,s,a)=>{G(a,r,n,s);const c=e.get(t(r,n));return(c?.revision??null)!==s?!1:(Y(c,a),e.set(t(r,n),k(a)),!0)}}}function be(e){let t,r;const n=new Promise((s,a)=>{t=s,r=a});return n.catch(()=>{}),{progress:e,promise:n,resolve:t,reject:r}}function W(e){const t=Promise.reject(e);return t.catch(()=>{}),t}function ve(e,t){let r,n,s=!0,a;function c(o){a??=o??new Error("任务心跳更新失败"),n?.reject(a),n=void 0,t(a)}function p(o){const u=Promise.resolve().then(()=>e(o?.progress)).then(()=>o?.resolve(),i=>{o?.reject(i),c(i)}).finally(()=>{r=void 0;const i=n;n=void 0,i&&a===void 0&&p(i)});r=u,u.catch(i=>{c(i)})}function w(o){if(!s)return W(new g("closed","任务进度上报已结束"));if(a!==void 0)return W(a);n?n.progress=o:n=be(o);const u=n;return r||(n=void 0,p(u)),u.promise}return{progress:w,pulse(){s&&!r&&a===void 0&&p()},async finish(){for(s=!1;r;)await r;if(a!==void 0)throw a}}}function xe(e){const t=e.concurrency??1,r=e.leaseMs??3e4,n=e.heartbeatMs??1e4;if(![t,r,n].every(a=>Number.isSafeInteger(a)&&a>0)||n*2>r)throw new g("input","并发、租约、心跳应为正整数,租约至少为心跳的两倍");if(n>2147483647)throw new g("input","心跳间隔超过平台定时器上限(2147483647毫秒)");return{concurrency:t,leaseMs:r,heartbeatMs:n,now:()=>{const a=(e.now??Date.now)();if(!Number.isSafeInteger(a)||a<0)throw new g("input","任务时钟应返回非负整数毫秒");return a}}}async function j(e,t,r){J(t,r);const n=await e.read(t,r);if(!n)throw new g("missing","报表任务不存在");return n}async function D(e,t,r,n=[]){const s={...t,...r,revision:t.revision+1};for(const a of n)delete s[a];if(!await e.compareAndSwap(t.scope,t.id,t.revision,s))throw new g("conflict","报表任务已被其他请求更新");return k(s)}async function Re(e,t,r,n){if(!t.trim()||t.length>128)throw new g("input","workerId应为1–128位标识");for(const s of await e.store.candidates(n,100)){const a=s.cancelRequested?{...s,revision:s.revision+1,status:"cancelled",updatedAt:n}:{...s,revision:s.revision+1,status:"running",attempt:s.attempt+1,updatedAt:n,lease:{owner:t,expiresAt:n+r}};if(a.status==="cancelled"&&delete a.lease,await e.store.compareAndSwap(s.scope,s.id,s.revision,a))return a}}function Z(e,t,r,n){return e.status==="running"&&e.attempt===n&&e.lease?.owner===t&&e.lease.expiresAt>r}async function Se(e,t,r,n){const{scope:s,id:a}=r,c=r.lease.owner;let p;const w=ve(async l=>{const y=await j(e.store,s,a),R=t.now();if(!Z(y,c,R,r.attempt))throw new g("lease","报表任务租约已失效");if(y.cancelRequested){n.abort(new g("closed","任务已请求取消"));return}await D(e.store,y,{updatedAt:R,lease:{owner:c,expiresAt:R+t.leaseMs},...l?{progress:l}:{}})},l=>{p=l,n.abort(l)}),o=(l,y)=>!Number.isFinite(l)||l<0||y!==void 0&&(!Number.isFinite(y)||y<l)?Promise.reject(new g("input","任务进度无效")):w.progress({completed:l,...y===void 0?{}:{total:y}}),u=setInterval(()=>{w.pulse()},t.heartbeatMs);let i,f;try{const l=Object.prototype.hasOwnProperty.call(e.handlers,r.request.kind)?e.handlers[r.request.kind]:void 0;if(!l)throw new g("input","报表任务处理器未注册");if(!n.signal.aborted){const y=await l(k(r.request.payload),{scope:s,taskId:a,signal:n.signal,reportProgress:o});i=JSON.parse(O(y))}}catch(l){f=l??new Error("报表任务执行失败")}finally{clearInterval(u),await w.finish().catch(()=>{})}const m=await j(e.store,s,a),h=t.now();if(!Z(m,c,h,r.attempt))throw new g("lease","任务租约已失效,迟到结果不会写回");const d={...m,updatedAt:h};delete d.lease,f??=p,m.cancelRequested||n.signal.aborted&&p===void 0?d.status="cancelled":f!==void 0?(d.status="failed",d.error={code:f instanceof g?f.code:"execution",message:f instanceof Error?f.message.slice(0,500):"报表任务执行失败"}):(d.status="completed",d.result=i);try{O(d)}catch{delete d.result,d.status="failed",d.error={code:"result-budget",message:"任务结果与请求的组合超过存储预算,请保存文件引用而非文件内容"}}return D(e.store,m,d,["lease"])}function Pe(e){e={...e,handlers:{...e.handlers}};const t=xe(e),r=new Set,n=new Set;let s=!1,a=0;const c=(o,u)=>(J(o,u),e.store.read(o,u));return{submit:async o=>{if(o={...o},J(o.scope,o.id),!Object.prototype.hasOwnProperty.call(e.handlers,o.kind)||typeof e.handlers[o.kind]!="function")throw new g("input","报表任务处理器未注册");const u={kind:o.kind,payload:JSON.parse(O(o.payload,{maxNodes:19e3,maxCharacters:75e4}))},i=await c(o.scope,o.id);if(i){if(O(i.request)!==O(u))throw new g("conflict","相同任务ID不能提交不同内容");return i}const f=t.now(),m={scope:o.scope,id:o.id,revision:1,request:u,status:"queued",createdAt:f,updatedAt:f,attempt:0};if(!await e.store.compareAndSwap(o.scope,o.id,null,m)){const h=await c(o.scope,o.id);if(h&&O(h.request)===O(u))return h;throw new g("conflict","报表任务提交发生冲突")}return k(m)},get:c,runNext:o=>{if(s)return Promise.reject(new g("closed","报表任务服务已关闭"));if(a>=t.concurrency)return Promise.reject(new g("capacity","报表任务执行槽位已满"));a++;const u=new AbortController;r.add(u);const i=(async()=>{try{const f=await Re(e,o,t.leaseMs,t.now());return f?.status==="running"?await Se(e,t,f,u):f}finally{a--,r.delete(u)}})();return n.add(i),i.then(()=>n.delete(i),()=>n.delete(i)),i},get active(){return a},cancel:async(o,u)=>{const i=await j(e.store,o,u);return i.status!=="queued"&&i.status!=="running"?i:D(e.store,i,{updatedAt:t.now(),...i.status==="queued"?{status:"cancelled"}:{cancelRequested:!0}})},retry:async(o,u,i)=>{const f=await j(e.store,o,u);if(f.revision!==i||f.status!=="failed")throw new g("conflict","仅可重试当前修订的失败任务");const m={...f,status:"queued",updatedAt:t.now()};return delete m.error,delete m.progress,D(e.store,f,m,["error","progress"])},close:async()=>{s=!0;for(const o of r)o.abort(new g("closed","任务服务正在关闭"));await Promise.allSettled([...n])}}}async function Oe(e,t,r,n,s){const a=U({reports:{sample:{template:r,datasets:e.datasets}},budget:{maxRows:n.maxSampleRows??1e3,maxPages:n.maxPages??100,maxCells:n.maxCells??25e4,maxOutputBytes:n.maxOutputBytes??2e7,maxDurationMs:n.maxDurationMs??3e4}});try{const{result:c,digest:p}=await L(n.signal,n.maxDurationMs??3e4,async w=>{const o=await a.export({reportIds:["sample"],params:e.params,format:t,signal:w,fidelity:n.strictOutputs?"strict":"warn",pdf:n.pdf,xlsx:n.xlsx}),u=await n.digest?.(o.bytes);return x(w),await n.onArtifact?.(e.name,o),x(w),{result:o,digest:u}});s.artifacts.push({fixture:e.name,format:t,bytes:c.bytes.byteLength,pages:c.stats.pages,...p?{digest:p}:{}}),s.issues.push(...c.diagnostics.map(w=>({code:"output",severity:n.strictOutputs||w.severity==="error"?"error":"warning",path:"$.template",fixture:e.name,message:`${t}: ${w.message}`})))}catch(c){x(n.signal),s.issues.push({code:"output",severity:"error",path:"$.template",fixture:e.name,message:`${t}: ${c instanceof Error?c.message:String(c)}`})}}async function Ee(e,t={}){x(t.signal);const r={...P.reviewReportTemplate(e,t),artifacts:[]};if(!r.ok)return r;const n=le.parseReportTemplate(e).template,s=[...new Set(t.formats??["pdf","xlsx"])];if(!s.length||s.some(a=>a!=="pdf"&&a!=="xlsx"))return r.issues.push({code:"output",severity:"error",path:"$.formats",message:"真实文件验收应至少选择PDF/XLSX之一"}),r.ok=!1,r;for(const a of t.fixtures??[])for(const c of s)x(t.signal),!(c!=="pdf"&&c!=="xlsx")&&await Oe(a,c,n,t,r);return r.ok=r.issues.every(a=>a.severity!=="error"),r}exports.ReportJobError=g;exports.ReportRuntimeError=v;exports.checkJobIdentity=J;exports.cloneJob=k;exports.createMemoryReportJobStore=ye;exports.createReportJobService=Pe;exports.createReportRuntime=U;exports.createReportTaskQueue=we;exports.reviewReportPublication=Ee;exports.runnableJob=ee;exports.serializeJobJson=O;exports.validateJobRecord=G;exports.validateJobTransition=Y;
|
|
1
|
+
"use strict";var te=Object.create;var C=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ne=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var ae=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of se(t))!oe.call(e,r)&&r!==s&&C(e,r,{get:()=>t[r],enumerable:!(o=re(t,r))||o.enumerable});return e};var z=(e,t,s)=>(s=e!=null?te(ne(e)):{},ae(t||!e||!e.__esModule?C(s,"default",{value:e,enumerable:!0}):s,e));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("./template-review-BXp2oyqg.cjs"),M=require("./report-params-SJfhKgaX.cjs"),O=require("./param-validation-DCK039V1.cjs"),a=require("./batch-Bt3Fd27Z.cjs"),H=require("./validate-DsRiNw8h.cjs"),S=require("./task-events-DQ276fn2.cjs"),ie=require("./tree-BX380Rr7.cjs"),ce=require("./template-registry-ukBGlm8N.cjs");function X(e,t){if(t.length===0)throw new x.ReportSourceError("config","缺少报表 ID");return t.map(s=>{if(!Object.prototype.hasOwnProperty.call(e,s))throw new x.ReportSourceError("config",`未注册报表 ${s}`);return e[s]})}function B(e){return e.grid?.cells.reduce((t,s)=>t+s.reduce((o,r)=>o+(r?1+(r.children?.reduce((n,c)=>n+B(c),0)??0):0),0),0)??0}function le(e,t,s){return e.pages.reduce((o,r)=>o+r.components.reduce((n,c)=>c.kind!=="list"?n+B(c):n+x.estimateListLayoutCells(c,t,s),0),0)}function ue(e,t=Object.keys(e)){return X(e,t).map((s,o)=>{const r=t[o],n=M.getReportParamDefs(e,[r]);return{id:r,params:JSON.parse(JSON.stringify(n)),paramsSchema:O.reportParamsJsonSchema(n),datasetsSchema:Object.fromEntries(Object.entries(s.dataSchema??{}).map(([c,u])=>[c,x.templateDatasetJsonSchema(typeof s.template=="function"?void 0:s.template,c,u)])),capabilities:{save:!!s.provider?.save,export:!!s.provider?.export,templates:!!s.provider?.templates}}})}function G(e,t){X(e,t.reportIds);const s=O.validateReportParams(M.getReportParamDefs(e,t.reportIds),t.params),o=t.reportIds.flatMap(n=>O.validateReportParams(M.getReportParamDefs(e,[n]),t.params).issues),r=new Map(o.map(n=>[JSON.stringify(n),n]));return{...s,ok:r.size===0,issues:[...r.values()]}}function de(e,t){const s=H.validateRenderPlan(e.plan);if(!s.ok)throw new x.ReportSourceError("validate",`执行器返回无效计划:${s.errors[0]?.path}`);const o=e.plan.pages.length,r=e.plan.pages.reduce((n,c)=>n+c.components.reduce((u,f)=>u+B(f),0),0);return a.assertBudget(t,"maxRows",e.stats.rows),a.assertBudget(t,"maxPages",o),a.assertBudget(t,"maxCells",r),{...e,stats:{...e.stats,pages:o,cells:r}}}function $(e,t){const s=G(e.reports,t);if(!s.ok)throw new O.ReportParamValidationError(s.issues)}function L(e=[]){return e.map(({code:t,message:s,severity:o,pageIndex:r,componentIndex:n})=>({code:t,message:s,severity:o,path:`$.pages.${r}.components.${n}`}))}function K(e){const t={maxRows:1e5,maxPages:1e4,maxCells:2e6,maxOutputBytes:1e8,maxDurationMs:12e4,...e.budget};a.validateBudget(t);const s=async(r,n)=>{const c=Date.now();let u=0,f=0;const i=[],d={},l=Object.fromEntries(Object.entries(e.reports).map(([g,w])=>[g,{...w,pagination:{...e.pagination,...w.pagination,...n?{measurer:n}:{}},dataValidation:{...w.dataValidation,maxRows:Math.min(t.maxRows??1e5,w.dataValidation?.maxRows??1/0),maxCells:Math.min(t.maxCells??2e6,w.dataValidation?.maxCells??1/0)}}])),h=await x.createReportSource(l,{maxPages:t.maxPages,maxCells:t.maxCells,onDatasets:(g,w,b)=>{try{u+=x.templateDatasetRowCount(b,w,{maxRows:t.maxRows,signal:r.signal})}catch(R){throw R instanceof ie.TreeRowLimitError?new a.ReportRuntimeError("budget",R.message,"$.budget.maxRows"):R}a.assertBudget(t,"maxRows",u),f+=le(b,w,l[g]?.pagination),a.assertBudget(t,"maxCells",f)}})({...r,params:r.params??{},onWarning:g=>i.push({code:"layout",message:g,severity:"warning"}),onQueryResult:(g,w)=>{Object.defineProperty(d,g,{value:{revision:w.revision,sourceToken:w.sourceToken},enumerable:!0,writable:!0}),i.push(...w.diagnostics??[])}}).catch(g=>{throw g instanceof x.ReportSourceError&&g.cause instanceof x.PaginatePageLimitError?new a.ReportRuntimeError("budget",g.cause.message,"$.budget.maxPages"):g instanceof x.ReportSourceError&&g.cause instanceof x.PaginateCellLimitError?new a.ReportRuntimeError("budget",g.cause.message,"$.budget.maxCells"):g});a.throwIfAborted(r.signal);const y=h.pages.reduce((g,w)=>g+w.components.reduce((b,R)=>b+B(R),0),0);return a.assertBudget(t,"maxPages",h.pages.length),a.assertBudget(t,"maxCells",y),{plan:h,diagnostics:i,revisions:d,stats:{rows:u,pages:h.pages.length,cells:y,durationMs:Date.now()-c}}},o=async r=>{const n=Date.now();if(r.format!=="pdf"&&r.format!=="xlsx")throw new x.ReportSourceError("config","不支持的报表导出格式");const c=r.format==="pdf"?await import("@agile-team/mach-report/pdf"):void 0;a.throwIfAborted(r.signal);const u=r.pdf?.customFontFaces??(r.pdf?.customFontBytes?{regular:r.pdf.customFontBytes}:void 0),f=c&&u?c.createFontBytesMeasurer(u):void 0,i=await s(r,f);a.throwIfAborted(r.signal);const d=S.createReportTaskReporter(r),l=await d.run("encode",async()=>{let y;if(r.format==="pdf"){a.throwIfAborted(r.signal);const g=await c.renderPlanToPdf(i.plan,{...r.pdf,...f?{measurer:f}:{},fidelity:r.fidelity,signal:r.signal,onProgress:(w,b)=>d.progress("encode",w,b)});y=g.bytes,i.diagnostics.push(...L(g.diagnostics)),i.diagnostics.push(...g.fidelityWarnings.map(w=>({code:"pdf-fidelity",message:w,severity:"warning"})),...g.pageErrors.map(w=>({code:"pdf-page",message:w,severity:"error"}))),g.unsupportedTextCount&&i.diagnostics.push({code:"pdf-font",message:`${g.unsupportedTextCount} 段文本包含当前字体不支持的字符`,severity:"warning"})}else{const{renderPlanToXlsx:g}=await import("@agile-team/mach-report/xlsx");a.throwIfAborted(r.signal);const w=await g(i.plan,{...r.xlsx,fidelity:r.fidelity,signal:r.signal,onProgress:(b,R)=>d.progress("encode",b,R)});y=new Uint8Array(w.buffer),i.diagnostics.push(...L(w.diagnostics)),i.diagnostics.push(...w.warnings.map(b=>({code:"xlsx-fidelity",message:b,severity:"warning"})))}return a.throwIfAborted(r.signal),y});a.assertBudget(t,"maxOutputBytes",l.byteLength);const{plan:p,...h}=i;return{...h,bytes:l,format:r.format,contentType:r.format==="pdf"?"application/pdf":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",stats:{...h.stats,durationMs:Date.now()-n,outputBytes:l.byteLength}}};return{describe:r=>ue(e.reports,r),validate:r=>G(e.reports,r),prepare:r=>a.controlled(r.signal,t.maxDurationMs,async n=>{const{signal:c,onEvent:u,...f}=r,i={...f,taskId:f.taskId??S.reportTaskId()},d=u?h=>S.notifyReportTaskEvent(u,h,n):void 0,l={...i,signal:n,onEvent:d};S.createReportTaskReporter(l).runSync("validate",()=>$(e,i));const p=e.executor?await e.executor.prepare(i,n,d):await s(l);return de(p,t)}),export:r=>a.controlled(r.signal,t.maxDurationMs,async n=>{const{signal:c,onEvent:u,...f}=r,i={...f,taskId:f.taskId??S.reportTaskId()},d=u?h=>S.notifyReportTaskEvent(u,h,n):void 0,l={...i,signal:n,onEvent:d};if(S.createReportTaskReporter(l).runSync("validate",()=>$(e,i)),i.format!=="pdf"&&i.format!=="xlsx")throw new x.ReportSourceError("config","不支持的报表导出格式");const p=e.executor?await e.executor.export(i,n,d):await o(l);return a.assertBudget(t,"maxRows",p.stats.rows),a.assertBudget(t,"maxPages",p.stats.pages),a.assertBudget(t,"maxCells",p.stats.cells),a.assertBudget(t,"maxOutputBytes",p.bytes.byteLength),{...p,stats:{...p.stats,outputBytes:p.bytes.byteLength}}})}}function pe(e={}){const t=e.concurrency??1,s=e.maxQueued??20;if(!Number.isSafeInteger(t)||t<1||!Number.isSafeInteger(s)||s<0)throw new a.ReportRuntimeError("budget","队列并发应为正整数,排队上限应为非负整数");const o=[],r=new Set,n=new Set;let c=!1,u=0;const f=()=>{for(;!c&&r.size<t&&o.length;)o.shift().start()};return{get active(){return r.size},get pending(){return o.length},enqueue:(d,l={})=>{if(c)throw new a.ReportRuntimeError("closed","任务队列已关闭");if(a.throwIfAborted(l.signal),r.size>=t&&o.length>=s)throw new a.ReportRuntimeError("queue-full","报表任务队列已满");const p=l.id??`report-${++u}`;if(n.has(p))throw new a.ReportRuntimeError("queue-full","任务 ID 正在执行或排队");const h=new AbortController;let y="queued",g,w,b,R;const Y=new Promise((v,I)=>{b=v,R=I}),q=()=>{l.signal?.removeEventListener("abort",D),n.delete(p)},P=v=>{if(y!=="queued"&&y!=="running")return;const I=y==="queued";y="cancelled",h.abort(v);try{a.throwIfAborted(h.signal)}catch(k){R(k)}if(I){const k=o.indexOf(E);k>=0&&o.splice(k,1),q(),f()}},D=()=>P(l.signal?.reason),T=(v,I)=>{y==="running"&&(!Number.isFinite(v)||v<0||I!==void 0&&(!Number.isFinite(I)||I<v)||(g={completed:v,...I===void 0?{}:{total:I}}))},ee=v=>{y!=="running"||h.signal.aborted||(w={...v,taskId:p},v.completed!==void 0&&T(v.completed,v.total),S.notifyReportTaskEvent(l.onEvent,w,h.signal))},E={cancel:P,start:()=>{y==="queued"&&(y="running",r.add(E),Promise.resolve().then(()=>(a.throwIfAborted(h.signal),d({taskId:p,signal:h.signal,reportProgress:T,onEvent:ee}))).then(v=>{y==="running"&&(y="completed",b(v))},v=>{y==="running"&&(y="failed",R(v))}).finally(()=>{r.delete(E),q(),f()}))}};return n.add(p),l.signal?.addEventListener("abort",D,{once:!0}),l.signal?.aborted?P(l.signal.reason):r.size<t?E.start():o.push(E),{id:p,get status(){return y},get progress(){return g},get event(){return w},result:Y,cancel:P}},close:(d={})=>{c=!0;for(const l of[...o])l.cancel(new a.ReportRuntimeError("closed","任务队列已关闭"));if(d.cancelRunning)for(const l of r)l.cancel(new a.ReportRuntimeError("closed","任务队列已关闭"))}}}function fe(e){let t,s;const o=new Promise((r,n)=>{t=r,s=n});return o.catch(()=>{}),{progress:e,promise:o,resolve:t,reject:s}}function F(e){const t=Promise.reject(e);return t.catch(()=>{}),t}function we(e,t){let s,o,r=!0,n;function c(i){n??=i??new Error("任务心跳更新失败"),o?.reject(n),o=void 0,t(n)}function u(i){const d=Promise.resolve().then(()=>e(i?.progress)).then(()=>i?.resolve(),l=>{i?.reject(l),c(l)}).finally(()=>{s=void 0;const l=o;o=void 0,l&&n===void 0&&u(l)});s=d,d.catch(l=>{c(l)})}function f(i){if(!r)return F(new a.ReportJobError("closed","任务进度上报已结束"));if(n!==void 0)return F(n);o?o.progress=i:o=fe(i);const d=o;return s||(o=void 0,u(d)),d.promise}return{progress:f,pulse(){r&&!s&&n===void 0&&u()},async finish(){for(r=!1;s;)await s;if(n!==void 0)throw n}}}class _ extends a.ReportJobError{constructor(){super("capacity","任务结果超过独立预算,请保存文件引用")}}function ge(e){const t=e.concurrency??1,s=e.leaseMs??3e4,o=e.heartbeatMs??1e4,r=e.maxResultBytes??256*1024;if(![t,s,o,r].every(c=>Number.isSafeInteger(c)&&c>0)||o*2>s)throw new a.ReportJobError("input","并发、租约、心跳应为正整数,租约至少为心跳的两倍");if(o>2147483647)throw new a.ReportJobError("input","心跳间隔超过平台定时器上限(2147483647毫秒)");return{concurrency:t,leaseMs:s,heartbeatMs:o,maxResultBytes:r,now:()=>{const c=(e.now??Date.now)();if(!Number.isSafeInteger(c)||c<0)throw new a.ReportJobError("input","任务时钟应返回非负整数毫秒");return c}}}async function N(e,t,s){a.checkJobIdentity(t,s);const o=await e.read(t,s);if(!o)throw new a.ReportJobError("missing","报表任务不存在");return o}async function A(e,t,s,o=[]){const r={...t,...s,revision:t.revision+1};for(const n of o)delete r[n];if(!await e.compareAndSwap(t.scope,t.id,t.revision,r))throw new a.ReportJobError("conflict","报表任务已被其他请求更新");return a.cloneJob(r)}async function me(e,t,s,o){if(!t.trim()||t.length>128)throw new a.ReportJobError("input","workerId应为1–128位标识");for(const r of await e.store.candidates(o,100)){const n=r.cancelRequested?{...r,revision:r.revision+1,status:"cancelled",updatedAt:o}:{...r,revision:r.revision+1,status:"running",attempt:r.attempt+1,updatedAt:o,lease:{owner:t,expiresAt:o+s}};if(n.status==="cancelled"&&delete n.lease,await e.store.compareAndSwap(r.scope,r.id,r.revision,n))return n}}function V(e,t,s,o){return e.status==="running"&&e.attempt===o&&e.lease?.owner===t&&e.lease.expiresAt>s}async function he(e,t,s,o){const{scope:r,id:n}=s,c=s.lease.owner;let u;const f=we(async w=>{const b=await N(e.store,r,n),R=t.now();if(!V(b,c,R,s.attempt))throw new a.ReportJobError("lease","报表任务租约已失效");if(b.cancelRequested){o.abort(new a.ReportJobError("closed","任务已请求取消"));return}await A(e.store,b,{updatedAt:R,lease:{owner:c,expiresAt:R+t.leaseMs},...w?{progress:w}:{}})},w=>{u=w,o.abort(w)}),i=(w,b)=>!Number.isFinite(w)||w<0||b!==void 0&&(!Number.isFinite(b)||b<w)?Promise.reject(new a.ReportJobError("input","任务进度无效")):f.progress({completed:w,...b===void 0?{}:{total:b}}),d=setInterval(()=>{f.pulse()},t.heartbeatMs);let l,p;try{const w=Object.prototype.hasOwnProperty.call(e.handlers,s.request.kind)?e.handlers[s.request.kind]:void 0;if(!w)throw new a.ReportJobError("input","报表任务处理器未注册");if(!o.signal.aborted){const b=await w(a.cloneJob(s.request.payload),{scope:r,taskId:n,signal:o.signal,reportProgress:i});if(l=JSON.parse(a.serializeJobJson(b)),a.jobByteLength(l)>t.maxResultBytes)throw new _}}catch(w){p=w??new Error("报表任务执行失败")}finally{clearInterval(d),await f.finish().catch(()=>{})}const h=await N(e.store,r,n),y=t.now();if(!V(h,c,y,s.attempt))throw new a.ReportJobError("lease","任务租约已失效,迟到结果不会写回");const g={...h,updatedAt:y};delete g.lease,p??=u,h.cancelRequested||o.signal.aborted&&u===void 0?g.status="cancelled":p!==void 0?(g.status="failed",g.error={code:p instanceof _?"result-budget":p instanceof a.ReportJobError?p.code:"execution",message:p instanceof Error?p.message.slice(0,500):"报表任务执行失败"}):(g.status="completed",g.result=l);try{a.serializeJobJson(g)}catch{delete g.result,g.status="failed",g.error={code:"result-budget",message:"任务结果与请求的组合超过存储预算,请保存文件引用而非文件内容"}}return ye(e.store,h,g)}async function ye(e,t,s){try{return await A(e,t,s,["lease"])}catch(o){if(!(o instanceof a.ReportJobError)||o.code!=="capacity"||s.status!=="completed")throw o;return delete s.result,s.status="failed",s.error={code:"result-budget",message:"任务结果超过存储剩余预算,请保存文件引用而非文件内容"},A(e,t,s,["lease","result"])}}async function Q(e,t,s,o){const r=await e.readTombstone?.(t,s);if(r){if(r.requestHash!==await a.jobRequestHash(o))throw new a.ReportJobError("conflict","相同任务ID不能提交不同内容(幂等保留期内)");return{scope:t,id:s,revision:r.revision,request:a.cloneJob(o),status:r.status,createdAt:r.createdAt,updatedAt:r.updatedAt,attempt:r.attempt,archived:!0}}}function be(e){e={...e,handlers:{...e.handlers}};const t=ge(e),s=new Set,o=new Set;let r=!1,n=0;const c=(i,d)=>(a.checkJobIdentity(i,d),e.store.read(i,d));return{submit:async i=>{if(i={...i},a.checkJobIdentity(i.scope,i.id),!Object.prototype.hasOwnProperty.call(e.handlers,i.kind)||typeof e.handlers[i.kind]!="function")throw new a.ReportJobError("input","报表任务处理器未注册");const d={kind:i.kind,payload:JSON.parse(a.serializeJobJson(i.payload,{maxNodes:19e3,maxCharacters:75e4}))},l=await c(i.scope,i.id);if(l){if(a.serializeJobJson(l.request)!==a.serializeJobJson(d))throw new a.ReportJobError("conflict","相同任务ID不能提交不同内容");return l}const p=await Q(e.store,i.scope,i.id,d);if(p)return p;const h=t.now(),y={scope:i.scope,id:i.id,revision:1,request:d,status:"queued",createdAt:h,updatedAt:h,attempt:0};if(!await e.store.compareAndSwap(i.scope,i.id,null,y)){const g=await c(i.scope,i.id);if(g&&a.serializeJobJson(g.request)===a.serializeJobJson(d))return g;const w=await Q(e.store,i.scope,i.id,d);if(w)return w;throw new a.ReportJobError("conflict","报表任务提交发生冲突")}return a.cloneJob(y)},get:c,runNext:i=>{if(r)return Promise.reject(new a.ReportJobError("closed","报表任务服务已关闭"));if(n>=t.concurrency)return Promise.reject(new a.ReportJobError("capacity","报表任务执行槽位已满"));n++;const d=new AbortController;s.add(d);const l=(async()=>{try{const p=await me(e,i,t.leaseMs,t.now());return p?.status==="running"?await he(e,t,p,d):p}finally{n--,s.delete(d)}})();return o.add(l),l.then(()=>o.delete(l),()=>o.delete(l)),l},get active(){return n},cancel:async(i,d)=>{const l=await N(e.store,i,d);return l.status!=="queued"&&l.status!=="running"?l:A(e.store,l,{updatedAt:t.now(),...l.status==="queued"?{status:"cancelled"}:{cancelRequested:!0}})},retry:async(i,d,l)=>{const p=await N(e.store,i,d);if(p.revision!==l||p.status!=="failed")throw new a.ReportJobError("conflict","仅可重试当前修订的失败任务");const h={...p,status:"queued",updatedAt:t.now()};return delete h.error,delete h.progress,A(e.store,p,h,["error","progress"])},prune:async i=>{if(!e.store.prune||!e.store.readTombstone)throw new a.ReportJobError("input","当前任务适配器未实现可选终态保留能力");return e.store.prune({...i,now:t.now()})},close:async()=>{r=!0;for(const i of s)i.abort(new a.ReportJobError("closed","任务服务正在关闭"));await Promise.allSettled([...o])}}}async function ve(e,t,s,o,r){const n=K({reports:{sample:{template:s,datasets:e.datasets}},budget:{maxRows:o.maxSampleRows??1e3,maxPages:o.maxPages??100,maxCells:o.maxCells??25e4,maxOutputBytes:o.maxOutputBytes??2e7,maxDurationMs:o.maxDurationMs??3e4}});try{const{result:c,digest:u}=await a.controlled(o.signal,o.maxDurationMs??3e4,async f=>{const i=await n.export({reportIds:["sample"],params:e.params,format:t,signal:f,fidelity:o.strictOutputs?"strict":"warn",pdf:o.pdf,xlsx:o.xlsx}),d=await o.digest?.(i.bytes);return a.throwIfAborted(f),await o.onArtifact?.(e.name,i),a.throwIfAborted(f),{result:i,digest:d}});r.artifacts.push({fixture:e.name,format:t,bytes:c.bytes.byteLength,pages:c.stats.pages,...u?{digest:u}:{}}),r.issues.push(...c.diagnostics.map(f=>({code:"output",severity:o.strictOutputs||f.severity==="error"?"error":"warning",path:"$.template",fixture:e.name,message:`${t}: ${f.message}`})))}catch(c){a.throwIfAborted(o.signal),r.issues.push({code:"output",severity:"error",path:"$.template",fixture:e.name,message:`${t}: ${c instanceof Error?c.message:String(c)}`})}}async function Re(e,t={}){a.throwIfAborted(t.signal);const s={...x.reviewReportTemplate(e,t),artifacts:[]};if(!s.ok)return s;const o=ce.parseReportTemplate(e).template,r=[...new Set(t.formats??["pdf","xlsx"])];if(!r.length||r.some(n=>n!=="pdf"&&n!=="xlsx"))return s.issues.push({code:"output",severity:"error",path:"$.formats",message:"真实文件验收应至少选择PDF/XLSX之一"}),s.ok=!1,s;for(const n of t.fixtures??[])for(const c of r)a.throwIfAborted(t.signal),!(c!=="pdf"&&c!=="xlsx")&&await ve(n,c,o,t,s);return s.ok=s.issues.every(n=>n.severity!=="error"),s}class m extends Error{constructor(t,s){super(s),this.code=t,this.name="ReportInstanceError"}}function J(e,t){if(typeof e!="string"||!e.trim()||e.length>256||typeof t!="string"||t!==t.trim()||!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(t))throw new m("input","实例范围应非空,ID 应为安全标识")}function xe(e,t){const s=Object.getPrototypeOf(e);if(s!==(t?Array.prototype:Object.prototype)&&!(s===null&&!t)||Object.getOwnPropertySymbols(e).length)throw new m("input","实例应为普通 JSON 对象");if(t&&Object.keys(e).some(r=>r!==String(Number(r))||!Number.isSafeInteger(Number(r))||Number(r)<0||Number(r)>=e.length))throw new m("input","实例数组不能包含额外属性");if(Object.getOwnPropertyNames(e).filter(r=>!(t&&r==="length")).length!==Object.keys(e).length)throw new m("input","实例不能包含不可枚举属性")}function Ie(e){if(!Number.isSafeInteger(e.revision)||e.revision<1||typeof e.title!="string"||!e.title.trim()||e.title.length>512)throw new m("input","实例修订或标题无效");if(typeof e.generatedAt!="string"||!Number.isFinite(Date.parse(e.generatedAt))||new Date(e.generatedAt).toISOString()!==e.generatedAt)throw new m("input","实例生成时间应为 ISO UTC 字符串");if(!Array.isArray(e.reportIds)||!e.reportIds.length||e.reportIds.length>100||e.reportIds.some(t=>typeof t!="string"||!t.trim())||new Set(e.reportIds).size!==e.reportIds.length)throw new m("input","实例报表 ID 应非空且唯一")}function Se(e){return e===null||typeof e=="boolean"||typeof e=="string"||typeof e=="number"&&Number.isFinite(e)}function U(e,t){if(!Number.isSafeInteger(t)||t<1)throw new m("input","实例快照字节预算应为正安全整数");let s=0,o=0;const r=new Set;function n(u,f){if(++s>1e6||f>64)throw new m("capacity","实例节点或嵌套深度超过预算");if(typeof u=="string"&&(o+=u.length,o>t))throw new m("capacity","实例文本超过预算");if(Se(u))return u;if(!u||typeof u!="object"||r.has(u))throw new m("input","实例只允许无循环的有限 JSON 值");const i=Array.isArray(u);xe(u,i),r.add(u);const d=Object.create(null);for(const l of Object.keys(u)){if(o+=l.length,o>t)throw new m("capacity","实例文本超过预算");const p=Object.getOwnPropertyDescriptor(u,l);if(p.get||p.set||["__proto__","constructor","prototype"].includes(l))throw new m("input","实例不能包含访问器或危险键");p.value!==void 0&&(d[l]=n(p.value,f+1))}if(r.delete(u),i){if(Object.keys(d).length!==u.length)throw new m("input","实例数组不能稀疏或包含额外属性");return Array.from({length:u.length},(l,p)=>d[String(p)])}return d}const c=JSON.stringify(n(e,0));if(new TextEncoder().encode(c).byteLength>t)throw new m("capacity","实例快照超过单条字节预算");return c}function j(e,t=8e6){const s=JSON.parse(U(e,t));J(s.scope,s.id);const o=new Set(["scope","id","revision","title","generatedAt","reportIds","params","templateRevisions","sourceRevisions","plan","artifacts"]);if(Object.keys(s).some(n=>!o.has(n)))throw new m("input","实例包含未知字段");Ie(s);for(const n of[s.params,s.templateRevisions,s.sourceRevisions])if(!n||typeof n!="object"||Array.isArray(n)||Object.values(n).some(c=>typeof c!="string"))throw new m("input","实例参数和修订应为字符串字典");if(s.reportIds.some(n=>!s.templateRevisions[n]))throw new m("input","每张报表都应记录生成时的模板修订");const r=H.validateRenderPlan(s.plan);if(!r.ok)throw new m("input",`实例渲染计划无效:${r.errors[0]?.path}`);return s.artifacts!==void 0&&Je(s.artifacts),s}function Je(e){if(!Array.isArray(e)||e.length>100)throw new m("input","实例文件列表无效");const t=new Set;for(const s of e){if(!s||typeof s.key!="string"||!s.key.trim()||s.key.length>1024||t.has(s.key)||!["pdf","xlsx"].includes(s.format)||!Number.isSafeInteger(s.bytes)||s.bytes<0||s.sha256!==void 0&&(typeof s.sha256!="string"||s.sha256.length!==64||!/^[a-f0-9]{64}$/i.test(s.sha256)))throw new m("input","实例文件应包含唯一存储键、格式、大小和可选 SHA256");t.add(s.key)}}function W(e){const{scope:t,id:s,revision:o,title:r,generatedAt:n,reportIds:c,params:u,templateRevisions:f,sourceRevisions:i}=e;return JSON.parse(JSON.stringify({scope:t,id:s,revision:o,title:r,generatedAt:n,reportIds:c,params:u,templateRevisions:f,sourceRevisions:i}))}function Ee(e){const t={maxInstances:100,maxRevisions:20,maxBytes:64e6,maxSnapshotBytes:8e6,...e};if(Object.values(t).some(s=>!Number.isSafeInteger(s)||s<1))throw new m("input","实例存储预算应为正安全整数");return t}function Oe(e={}){const t=Ee(e),s=new Map;let o=0;return{read:async(r,n,c,u)=>{a.throwIfAborted(u),J(r,n);const f=s.get(JSON.stringify([r,n])),i=f?.get(c??f.size);return i?j(i,t.maxSnapshotBytes):void 0},list:async(r,n={})=>{a.throwIfAborted(n.signal),J(r,"list");const c=n.limit??25;if(!Number.isSafeInteger(c)||c<1||c>100)throw new m("input","实例每页数量应为 1–100");const u=Array.from(s.values(),f=>f.get(f.size)).filter(f=>f.scope===r&&(!n.after||f.id>n.after)&&(!n.reportId||f.reportIds.includes(n.reportId))).sort((f,i)=>f.id<i.id?-1:1).slice(0,c+1);return{items:u.slice(0,c).map(W),...u.length>c?{next:u[c-1].id}:{}}},compareAndSwap:async(r,n,c,u,f)=>{a.throwIfAborted(f),J(r,n);const i=j(u,t.maxSnapshotBytes);if(i.scope!==r||i.id!==n||i.revision!==(c??0)+1)throw new m("input","实例身份或修订不一致");const d=JSON.stringify([r,n]),l=s.get(d)??new Map;if((l.size||null)!==c)return!1;const p=new TextEncoder().encode(JSON.stringify(i)).byteLength;if(l.size>=t.maxRevisions||o+p>t.maxBytes||!l.size&&s.size>=t.maxInstances)throw new m("capacity","实例存储达到预算,请使用持久化归档策略或显式调整容量");return l.set(i.revision,i),s.set(d,l),o+=p,!0}}}function Z(e,t={}){const s=t.maxSnapshotBytes??8e6;if(!Number.isSafeInteger(s)||s<1)throw new m("input","实例快照字节预算应为正安全整数");return{read:e.read.bind(e),list:e.list.bind(e),save:async(o,r)=>{const n=r.expectedRevision;if(n!==null&&(!Number.isSafeInteger(n)||n<1||n>=Number.MAX_SAFE_INTEGER))throw new m("input","实例预期修订无效");const c=JSON.parse(U(o,s)),u=j({...c,revision:(n??0)+1},s);if(a.throwIfAborted(r.signal),!await e.compareAndSwap(u.scope,u.id,n,u,r.signal))throw new m("conflict","实例已存在或已变化,请显式确认修订后保存");return u}}}function Ae(e,t){return a.serializeJobJson({reportIds:e,params:t})}function Pe(e){if(!e||typeof e!="object"||Object.getOwnPropertySymbols(e).length)throw new m("input","生成请求应为普通对象");const t=Object.create(null);let s;const o=new Set(["scope","id","title","reportIds","params","expectedRevision","signal"]);for(const r of Object.getOwnPropertyNames(e)){const n=Object.getOwnPropertyDescriptor(e,r);if(!o.has(r)||!n.enumerable||n.get||n.set)throw new m("input","生成请求不能包含未知字段或访问器");r==="signal"?s=n.value:n.value!==void 0&&(t[r]=n.value)}return{signal:s,request:JSON.parse(a.serializeJobJson(t))}}function ke(e){const t=JSON.parse(a.serializeJobJson(e.templateRevisions)),s=Z(e.instances,{maxSnapshotBytes:e.maxSnapshotBytes});return{async generate(o){const{signal:r,request:n}=Pe(o);if(J(n.scope,n.id),n.expectedRevision!==null&&(!Number.isSafeInteger(n.expectedRevision)||n.expectedRevision<1))throw new m("input","实例预期修订无效");const c=e.runtime.validate(n);if(!c.ok)throw new O.ReportParamValidationError(c.issues);const u=Object.create(null);for(const l of n.reportIds){if(typeof t[l]!="string"||!t[l])throw new TypeError(`报表 ${l} 缺少生成时的模板修订`);u[l]=t[l]}if(a.throwIfAborted(r),((await e.instances.read(n.scope,n.id,void 0,r))?.revision??null)!==n.expectedRevision)throw new m("conflict","实例已存在或已变化,请显式确认修订后生成");const i=await e.runtime.prepare({reportIds:n.reportIds,params:c.values,signal:r});a.throwIfAborted(r);const d=Object.create(null);for(const[l,p]of Object.entries(i.revisions))p.revision!==void 0&&(d[l]=p.revision);return s.save({scope:n.scope,id:n.id,title:n.title,reportIds:n.reportIds,params:c.values,templateRevisions:u,sourceRevisions:d,generatedAt:(e.now?.()??new Date).toISOString(),plan:i.plan},{expectedRevision:n.expectedRevision,signal:r})},read:s.read,list:s.list,save:s.save}}exports.ReportJobError=a.ReportJobError;exports.ReportRuntimeError=a.ReportRuntimeError;exports.checkJobIdentity=a.checkJobIdentity;exports.cloneJob=a.cloneJob;exports.createMemoryReportJobStore=a.createMemoryReportJobStore;exports.runReportBatch=a.runReportBatch;exports.runnableJob=a.runnableJob;exports.serializeJobJson=a.serializeJobJson;exports.snapshotReportBatchCases=a.snapshotReportBatchCases;exports.validateJobRecord=a.validateJobRecord;exports.validateJobTransition=a.validateJobTransition;exports.ReportInstanceError=m;exports.checkInstanceIdentity=J;exports.createMemoryReportInstanceStore=Oe;exports.createReportGenerationService=ke;exports.createReportInstanceManager=Z;exports.createReportJobService=be;exports.createReportRuntime=K;exports.createReportTaskQueue=pe;exports.reportInstanceParameterKey=Ae;exports.reportInstanceSummary=W;exports.reviewReportPublication=Re;exports.snapshotReportInstance=j;
|
package/dist/server.d.cts
CHANGED
|
@@ -10,6 +10,8 @@ declare interface AggregateDefinition {
|
|
|
10
10
|
|
|
11
11
|
declare type AggregateOperation = "sum" | "count" | "avg" | "min" | "max" | "weightedAvg";
|
|
12
12
|
|
|
13
|
+
export declare function checkInstanceIdentity(scope: string, id: string): void;
|
|
14
|
+
|
|
13
15
|
export declare function checkJobIdentity(scope: string, id: string): void;
|
|
14
16
|
|
|
15
17
|
export declare function cloneJob<T>(value: T): T;
|
|
@@ -73,8 +75,41 @@ declare interface ComputedField extends ExpressionOptions {
|
|
|
73
75
|
dependsOn?: readonly string[];
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
/** Bounded reference store for tests/local sessions. Production hosts implement durable transactional CAS. */
|
|
79
|
+
export declare function createMemoryReportInstanceStore(options?: MemoryReportInstanceStoreOptions): ReportInstanceStore;
|
|
80
|
+
|
|
76
81
|
/** Reference store for tests; use a database CAS or the single-process durable Node adapter in production. */
|
|
77
|
-
export declare function createMemoryReportJobStore(): ReportJobStore;
|
|
82
|
+
export declare function createMemoryReportJobStore(options?: ReportJobStoreBudgetOptions): ReportJobStore;
|
|
83
|
+
|
|
84
|
+
/** Bind immutable template revisions to the same runtime registration. Rebuild the service on publication. */
|
|
85
|
+
export declare function createReportGenerationService(options: {
|
|
86
|
+
runtime: ReportRuntime;
|
|
87
|
+
instances: ReportInstanceStore;
|
|
88
|
+
templateRevisions: Record<string, string>;
|
|
89
|
+
maxSnapshotBytes?: number;
|
|
90
|
+
now?: () => Date;
|
|
91
|
+
}): {
|
|
92
|
+
generate(input: ReportGenerationInput): Promise<ReportInstanceSnapshot>;
|
|
93
|
+
read: (scope: string, id: string, revision?: number, signal?: AbortSignal) => Promise<ReportInstanceSnapshot | undefined>;
|
|
94
|
+
list: (scope: string, options?: ReportInstanceListOptions) => Promise<ReportInstancePage>;
|
|
95
|
+
/** Instance edits are explicit snapshots; source business persistence remains the Provider.save responsibility. */
|
|
96
|
+
save: (input: Omit<ReportInstanceSnapshot, "revision">, options: {
|
|
97
|
+
expectedRevision: number | null;
|
|
98
|
+
signal?: AbortSignal;
|
|
99
|
+
}) => Promise<ReportInstanceSnapshot>;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Save is explicit. A preview/query never calls this manager or overwrites a prior instance. */
|
|
103
|
+
export declare function createReportInstanceManager(store: ReportInstanceStore, options?: {
|
|
104
|
+
maxSnapshotBytes?: number;
|
|
105
|
+
}): {
|
|
106
|
+
read: (scope: string, id: string, revision?: number, signal?: AbortSignal) => Promise<ReportInstanceSnapshot | undefined>;
|
|
107
|
+
list: (scope: string, options?: ReportInstanceListOptions) => Promise<ReportInstancePage>;
|
|
108
|
+
save: (input: Omit<ReportInstanceSnapshot, "revision">, options: {
|
|
109
|
+
expectedRevision: number | null;
|
|
110
|
+
signal?: AbortSignal;
|
|
111
|
+
}) => Promise<ReportInstanceSnapshot>;
|
|
112
|
+
};
|
|
78
113
|
|
|
79
114
|
/** Durable at-least-once dispatch; export/artifact handlers must be idempotent by scope+taskId. */
|
|
80
115
|
export declare function createReportJobService(options: ReportJobServiceOptions): ReportJobService;
|
|
@@ -125,11 +160,15 @@ declare interface GridCell {
|
|
|
125
160
|
value?: string | number | boolean | null;
|
|
126
161
|
/** Excel 数字格式(例如 #,##0.00 / 0.00%),不改变 text。 */
|
|
127
162
|
numberFormat?: string;
|
|
163
|
+
/** Physical left inset for hierarchical labels; raw value stays unchanged. */
|
|
164
|
+
indentMm?: number;
|
|
128
165
|
/** 编辑输入类型;省略时只按原始值推断,数字字符串不会被转换为数值。 */
|
|
129
166
|
valueType?: "text" | "number" | "boolean";
|
|
130
167
|
/** 单元格级编辑控制;预览仍需显式开启 editing。 */
|
|
131
168
|
editable?: boolean;
|
|
132
169
|
source?: ReportCellSource;
|
|
170
|
+
/** SDK 派生结果不映射业务来源,始终只读(包括覆盖同名源字段的计算)。 */
|
|
171
|
+
derived?: boolean;
|
|
133
172
|
/** Excel 公式保留元数据;当前引擎不计算公式,公式格始终只读。 */
|
|
134
173
|
formula?: string;
|
|
135
174
|
children?: PlanComponent[];
|
|
@@ -169,6 +208,8 @@ declare interface ListColumn {
|
|
|
169
208
|
|
|
170
209
|
declare interface ListComponent {
|
|
171
210
|
kind: "list";
|
|
211
|
+
/** Opaque serializable layout annotations; interpreted only by explicit optional adapters. */
|
|
212
|
+
meta?: Record<string, unknown>;
|
|
172
213
|
id?: string;
|
|
173
214
|
/** 宽表标记用于连续 Excel;不改变低层列表的列宽和分页算法。 */
|
|
174
215
|
layout?: "page" | "wide";
|
|
@@ -178,6 +219,10 @@ declare interface ListComponent {
|
|
|
178
219
|
topMm: number;
|
|
179
220
|
widthMm: number;
|
|
180
221
|
dataset: string;
|
|
222
|
+
/** Headerless record/region bindings do not allocate a synthetic list header. */
|
|
223
|
+
header?: boolean;
|
|
224
|
+
/** A fixed record region rejects multiple rows instead of dropping source data. */
|
|
225
|
+
cardinality?: "record";
|
|
181
226
|
/** 数据集中稳定业务主键的字段名;省略时编辑仅有计划内坐标。 */
|
|
182
227
|
rowKey?: string;
|
|
183
228
|
revisionField?: string;
|
|
@@ -204,6 +249,8 @@ declare interface ListComponent {
|
|
|
204
249
|
computed?: ComputedField[];
|
|
205
250
|
/** Dynamic pivot of the current source dataset, with read-only derived identities. */
|
|
206
251
|
matrix?: MatrixTableOptions;
|
|
252
|
+
/** Full ordered hierarchy; source parent values and descendant-leaf rollups are explicit. */
|
|
253
|
+
tree?: TreeTableOptions;
|
|
207
254
|
/** Explicit row visibility; hidden rows do not participate in summaries. */
|
|
208
255
|
rowVisibleWhen?: StyleRule["when"];
|
|
209
256
|
/** Master/detail relation is keyed by business identity, never by display row index. */
|
|
@@ -217,6 +264,9 @@ declare interface ListDetail {
|
|
|
217
264
|
parentKey: string;
|
|
218
265
|
foreignKey: string;
|
|
219
266
|
rowKey?: string;
|
|
267
|
+
/** 子表独立的并发契约;省略时不继承主表字段。 */
|
|
268
|
+
revisionField?: string;
|
|
269
|
+
sourceTokenField?: string;
|
|
220
270
|
columns: ListColumn[];
|
|
221
271
|
rowHeightMm?: number;
|
|
222
272
|
header?: boolean;
|
|
@@ -267,6 +317,13 @@ declare interface MatrixTableOptions {
|
|
|
267
317
|
maxCells?: number;
|
|
268
318
|
}
|
|
269
319
|
|
|
320
|
+
export declare interface MemoryReportInstanceStoreOptions {
|
|
321
|
+
maxInstances?: number;
|
|
322
|
+
maxRevisions?: number;
|
|
323
|
+
maxBytes?: number;
|
|
324
|
+
maxSnapshotBytes?: number;
|
|
325
|
+
}
|
|
326
|
+
|
|
270
327
|
declare interface OutputDiagnostic {
|
|
271
328
|
code: "missing-image" | "unsupported-component" | "unsupported-image-format" | "unsupported-style" | "rich-text-flattened" | "nested-components-omitted" | "async-image-required" | "image-decode-failed" | "asset-render-failed" | "asset-budget-exceeded";
|
|
272
329
|
severity: "warning";
|
|
@@ -335,6 +392,7 @@ declare interface PlanGrid {
|
|
|
335
392
|
}
|
|
336
393
|
|
|
337
394
|
declare interface PlanPage {
|
|
395
|
+
meta?: Record<string, unknown>;
|
|
338
396
|
/** wide 表示完整自然列宽;页面仅为预览的垂直窗口分段。 */
|
|
339
397
|
layout?: "page" | "wide";
|
|
340
398
|
pageWidthMm: number;
|
|
@@ -375,9 +433,48 @@ export declare interface PreparedReport {
|
|
|
375
433
|
|
|
376
434
|
declare interface RenderPlan {
|
|
377
435
|
schemaVersion: string;
|
|
436
|
+
/** 编辑需要模板重算上下文;跨 Worker/JSON 保留要求,但不声明本地会话可用。 */
|
|
437
|
+
requiresRecalculation?: boolean;
|
|
378
438
|
pages: PlanPage[];
|
|
379
439
|
}
|
|
380
440
|
|
|
441
|
+
export declare interface ReportBatchCase {
|
|
442
|
+
id: string;
|
|
443
|
+
params: Record<string, string>;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export declare interface ReportBatchOptions {
|
|
447
|
+
concurrency?: number;
|
|
448
|
+
maxCases?: number;
|
|
449
|
+
signal?: AbortSignal;
|
|
450
|
+
/** Stop launching new cases after the first failure; already running cases finish. */
|
|
451
|
+
failure?: "continue" | "stop";
|
|
452
|
+
onProgress?: (progress: ReportBatchProgress) => void;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export declare type ReportBatchOutcome<T> = {
|
|
456
|
+
id: string;
|
|
457
|
+
index: number;
|
|
458
|
+
} & ({
|
|
459
|
+
status: "completed";
|
|
460
|
+
value: T;
|
|
461
|
+
} | {
|
|
462
|
+
status: "failed";
|
|
463
|
+
error: {
|
|
464
|
+
name: string;
|
|
465
|
+
message: string;
|
|
466
|
+
};
|
|
467
|
+
} | {
|
|
468
|
+
status: "cancelled";
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
export declare interface ReportBatchProgress {
|
|
472
|
+
completed: number;
|
|
473
|
+
total: number;
|
|
474
|
+
failed: number;
|
|
475
|
+
cancelled: number;
|
|
476
|
+
}
|
|
477
|
+
|
|
381
478
|
/** 预览中的物理坐标;合并单元格使用左上角坐标。 */
|
|
382
479
|
declare interface ReportCellAddress {
|
|
383
480
|
pageIndex: number;
|
|
@@ -479,6 +576,78 @@ declare interface ReportFieldContract {
|
|
|
479
576
|
unit?: string;
|
|
480
577
|
}
|
|
481
578
|
|
|
579
|
+
declare interface ReportFlowBand {
|
|
580
|
+
/** Exact stable component IDs. Static components use nid, lists use id. */
|
|
581
|
+
componentIds: string[];
|
|
582
|
+
/** Source-template coordinates; following bands keep their original gap. */
|
|
583
|
+
topMm: number;
|
|
584
|
+
heightMm: number;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export declare interface ReportGenerationInput {
|
|
588
|
+
scope: string;
|
|
589
|
+
id: string;
|
|
590
|
+
title: string;
|
|
591
|
+
reportIds: string[];
|
|
592
|
+
params?: Record<string, string>;
|
|
593
|
+
expectedRevision: number | null;
|
|
594
|
+
signal?: AbortSignal;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export declare interface ReportInstanceArtifact {
|
|
598
|
+
key: string;
|
|
599
|
+
format: "pdf" | "xlsx";
|
|
600
|
+
bytes: number;
|
|
601
|
+
sha256?: string;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export declare class ReportInstanceError extends Error {
|
|
605
|
+
readonly code: "input" | "conflict" | "capacity";
|
|
606
|
+
constructor(code: "input" | "conflict" | "capacity", message: string);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export declare interface ReportInstanceListOptions {
|
|
610
|
+
after?: string;
|
|
611
|
+
limit?: number;
|
|
612
|
+
reportId?: string;
|
|
613
|
+
signal?: AbortSignal;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export declare interface ReportInstancePage {
|
|
617
|
+
items: ReportInstanceSummary[];
|
|
618
|
+
next?: string;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Stable parameter identity without adding a database, user account or credential to the SDK. */
|
|
622
|
+
export declare function reportInstanceParameterKey(reportIds: string[], params: Record<string, string>): string;
|
|
623
|
+
|
|
624
|
+
export declare interface ReportInstanceSnapshot {
|
|
625
|
+
scope: string;
|
|
626
|
+
id: string;
|
|
627
|
+
revision: number;
|
|
628
|
+
title: string;
|
|
629
|
+
generatedAt: string;
|
|
630
|
+
reportIds: string[];
|
|
631
|
+
params: Record<string, string>;
|
|
632
|
+
/** Exact template/data revisions used by generation, never inferred from a later template read. */
|
|
633
|
+
templateRevisions: Record<string, string>;
|
|
634
|
+
sourceRevisions: Record<string, string>;
|
|
635
|
+
plan: RenderPlan;
|
|
636
|
+
/** Private storage keys; signed download URLs must be generated by the host after authorization. */
|
|
637
|
+
artifacts?: ReportInstanceArtifact[];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export declare interface ReportInstanceStore {
|
|
641
|
+
read(scope: string, id: string, revision?: number, signal?: AbortSignal): Promise<ReportInstanceSnapshot | undefined>;
|
|
642
|
+
list(scope: string, options?: ReportInstanceListOptions): Promise<ReportInstancePage>;
|
|
643
|
+
/** Atomic append + current-revision advance. Historical revisions must never be overwritten. */
|
|
644
|
+
compareAndSwap(scope: string, id: string, expectedRevision: number | null, next: ReportInstanceSnapshot, signal?: AbortSignal): Promise<boolean>;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export declare type ReportInstanceSummary = Omit<ReportInstanceSnapshot, "plan" | "artifacts">;
|
|
648
|
+
|
|
649
|
+
export declare function reportInstanceSummary(snapshot: ReportInstanceSnapshot): ReportInstanceSummary;
|
|
650
|
+
|
|
482
651
|
export declare class ReportJobError extends Error {
|
|
483
652
|
readonly code: "input" | "conflict" | "missing" | "lease" | "closed" | "capacity";
|
|
484
653
|
constructor(code: "input" | "conflict" | "missing" | "lease" | "closed" | "capacity", message: string);
|
|
@@ -497,6 +666,18 @@ export declare type ReportJobJson = null | boolean | number | string | ReportJob
|
|
|
497
666
|
[key: string]: ReportJobJson;
|
|
498
667
|
};
|
|
499
668
|
|
|
669
|
+
export declare interface ReportJobPruneOptions {
|
|
670
|
+
now: number;
|
|
671
|
+
terminalBefore: number;
|
|
672
|
+
tombstoneRetentionMs?: number;
|
|
673
|
+
limit?: number;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export declare interface ReportJobPruneResult {
|
|
677
|
+
archived: number;
|
|
678
|
+
expired: number;
|
|
679
|
+
}
|
|
680
|
+
|
|
500
681
|
export declare interface ReportJobRecord {
|
|
501
682
|
scope: string;
|
|
502
683
|
id: string;
|
|
@@ -516,6 +697,8 @@ export declare interface ReportJobRecord {
|
|
|
516
697
|
total?: number;
|
|
517
698
|
};
|
|
518
699
|
result?: ReportJobJson;
|
|
700
|
+
/** Idempotent submit after terminal archival; result/error payloads are no longer retained. */
|
|
701
|
+
archived?: boolean;
|
|
519
702
|
error?: {
|
|
520
703
|
code: string;
|
|
521
704
|
message: string;
|
|
@@ -539,6 +722,8 @@ export declare interface ReportJobService {
|
|
|
539
722
|
runNext(workerId: string): Promise<ReportJobRecord | undefined>;
|
|
540
723
|
/** Stops new dispatch, signals active handlers and waits until their actual completion. */
|
|
541
724
|
close(): Promise<void>;
|
|
725
|
+
/** Archive terminal payloads and retain bounded idempotency fingerprints; never removes active work. */
|
|
726
|
+
prune(options: Omit<ReportJobPruneOptions, "now">): Promise<ReportJobPruneResult>;
|
|
542
727
|
readonly active: number;
|
|
543
728
|
}
|
|
544
729
|
|
|
@@ -549,6 +734,8 @@ export declare interface ReportJobServiceOptions {
|
|
|
549
734
|
leaseMs?: number;
|
|
550
735
|
heartbeatMs?: number;
|
|
551
736
|
now?: () => number;
|
|
737
|
+
/** Result references have a separate byte budget; default 256KiB. */
|
|
738
|
+
maxResultBytes?: number;
|
|
552
739
|
}
|
|
553
740
|
|
|
554
741
|
export declare type ReportJobStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
@@ -557,9 +744,33 @@ export declare type ReportJobStatus = "queued" | "running" | "completed" | "fail
|
|
|
557
744
|
export declare interface ReportJobStore {
|
|
558
745
|
read(scope: string, id: string): Promise<ReportJobRecord | undefined>;
|
|
559
746
|
candidates(now: number, limit: number): Promise<ReportJobRecord[]>;
|
|
747
|
+
/** Optional bounded retention capability; existing database adapters remain compatible. */
|
|
748
|
+
readTombstone?(scope: string, id: string): Promise<ReportJobTombstone | undefined>;
|
|
749
|
+
prune?(options: ReportJobPruneOptions): Promise<ReportJobPruneResult>;
|
|
560
750
|
compareAndSwap(scope: string, id: string, expectedRevision: number | null, next: ReportJobRecord): Promise<boolean>;
|
|
561
751
|
}
|
|
562
752
|
|
|
753
|
+
export declare interface ReportJobStoreBudgetOptions {
|
|
754
|
+
maxRecords?: number;
|
|
755
|
+
maxBytes?: number;
|
|
756
|
+
maxResultBytes?: number;
|
|
757
|
+
controlReserveBytes?: number;
|
|
758
|
+
maxTombstones?: number;
|
|
759
|
+
maxTombstoneBytes?: number;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export declare interface ReportJobTombstone {
|
|
763
|
+
scope: string;
|
|
764
|
+
id: string;
|
|
765
|
+
revision: number;
|
|
766
|
+
status: Exclude<ReportJobStatus, "queued" | "running">;
|
|
767
|
+
createdAt: number;
|
|
768
|
+
updatedAt: number;
|
|
769
|
+
attempt: number;
|
|
770
|
+
requestHash: string;
|
|
771
|
+
expiresAt: number;
|
|
772
|
+
}
|
|
773
|
+
|
|
563
774
|
/** 取数与排版共同使用的请求上下文;取消只影响本次调用。 */
|
|
564
775
|
declare interface ReportLoadContext {
|
|
565
776
|
reportId: string;
|
|
@@ -567,13 +778,17 @@ declare interface ReportLoadContext {
|
|
|
567
778
|
signal?: AbortSignal;
|
|
568
779
|
}
|
|
569
780
|
|
|
781
|
+
declare interface ReportPageFlow {
|
|
782
|
+
bands: ReportFlowBand[];
|
|
783
|
+
}
|
|
784
|
+
|
|
570
785
|
/** 报表参数定义(声明式,模板自描述参数面:渲染组件据此生成查询面板) */
|
|
571
786
|
declare interface ReportParamDef {
|
|
572
787
|
/** 参数名(传入 params,与数据集 SQL #{field} 绑定对应) */
|
|
573
788
|
field: string;
|
|
574
789
|
/** 显示名(缺省用 field) */
|
|
575
790
|
label?: string;
|
|
576
|
-
type?: "text" | "number" | "date" | "select";
|
|
791
|
+
type?: "text" | "number" | "date" | "month" | "year" | "dateRange" | "select";
|
|
577
792
|
/** select 选项 */
|
|
578
793
|
options?: Array<{
|
|
579
794
|
label: string;
|
|
@@ -582,7 +797,7 @@ declare interface ReportParamDef {
|
|
|
582
797
|
defaultValue?: string;
|
|
583
798
|
required?: boolean;
|
|
584
799
|
placeholder?: string;
|
|
585
|
-
/**
|
|
800
|
+
/** 数字上下界;日期/月/年使用对应规范字符串;dateRange 对两端使用 YYYY-MM-DD 边界(含边界)。 */
|
|
586
801
|
min?: number | string;
|
|
587
802
|
max?: number | string;
|
|
588
803
|
minLength?: number;
|
|
@@ -874,7 +1089,7 @@ declare interface ReportTemplateReview {
|
|
|
874
1089
|
}
|
|
875
1090
|
|
|
876
1091
|
declare interface ReportTemplateReviewIssue {
|
|
877
|
-
code: "template" | "compatibility" | "dataset" | "field" | "sample" | "layout" | "output";
|
|
1092
|
+
code: "template" | "compatibility" | "dataset" | "field" | "sample" | "layout" | "output" | "params";
|
|
878
1093
|
severity: "error" | "warning";
|
|
879
1094
|
path: string;
|
|
880
1095
|
message: string;
|
|
@@ -919,12 +1134,22 @@ declare interface RichSegment {
|
|
|
919
1134
|
|
|
920
1135
|
export declare function runnableJob(record: ReportJobRecord, now: number): boolean;
|
|
921
1136
|
|
|
1137
|
+
/** Browser/Node shared bounded runner. Return small receipts; persist/export large artifacts inside execute. */
|
|
1138
|
+
export declare function runReportBatch<T>(input: readonly ReportBatchCase[], execute: (item: ReportBatchCase, context: {
|
|
1139
|
+
index: number;
|
|
1140
|
+
signal: AbortSignal;
|
|
1141
|
+
}) => Promise<T>, options?: ReportBatchOptions): Promise<ReportBatchOutcome<T>[]>;
|
|
1142
|
+
|
|
922
1143
|
/** Canonical plain JSON rejects getters/scripts and gives stable idempotency without coercion. */
|
|
923
1144
|
export declare function serializeJobJson(value: unknown, budget?: {
|
|
924
1145
|
maxNodes?: number;
|
|
925
1146
|
maxCharacters?: number;
|
|
926
1147
|
}): string;
|
|
927
1148
|
|
|
1149
|
+
export declare function snapshotReportBatchCases(input: readonly ReportBatchCase[], maximum?: number): ReportBatchCase[];
|
|
1150
|
+
|
|
1151
|
+
export declare function snapshotReportInstance(value: ReportInstanceSnapshot, maxBytes?: number): ReportInstanceSnapshot;
|
|
1152
|
+
|
|
928
1153
|
/** 条件格式规则求值(声明式,见 ListColumn.rules) */
|
|
929
1154
|
declare interface StyleRule {
|
|
930
1155
|
when: {
|
|
@@ -950,6 +1175,9 @@ declare interface TemplatePage {
|
|
|
950
1175
|
marginLeftMm?: number;
|
|
951
1176
|
marginRightMm?: number;
|
|
952
1177
|
components: TemplateComponent[];
|
|
1178
|
+
/** Explicit vertical bands preserve source gaps as dynamic regions expand. */
|
|
1179
|
+
flow?: ReportPageFlow;
|
|
1180
|
+
meta?: Record<string, unknown>;
|
|
953
1181
|
}
|
|
954
1182
|
|
|
955
1183
|
/**
|
|
@@ -972,6 +1200,26 @@ declare interface TextMeasurer {
|
|
|
972
1200
|
measureMm(text: string, options: TextMeasureOptions): number;
|
|
973
1201
|
}
|
|
974
1202
|
|
|
1203
|
+
/** A serialized tree contract. Sibling and root order follow input order. */
|
|
1204
|
+
declare interface TreeTableOptions {
|
|
1205
|
+
input: "flat" | "nested";
|
|
1206
|
+
idField: string;
|
|
1207
|
+
/** Flat roots have null/undefined parent values. */
|
|
1208
|
+
parentField?: string;
|
|
1209
|
+
/** Nested child arrays, defaults to children. Every node still needs an ID. */
|
|
1210
|
+
childrenField?: string;
|
|
1211
|
+
labelField: string;
|
|
1212
|
+
indentMm?: number;
|
|
1213
|
+
orphan?: "error" | "root";
|
|
1214
|
+
/** Source preserves parent values; leaf-aggregate derives only parents from descendant leaves. */
|
|
1215
|
+
parentValues?: "source" | "leaf-aggregate";
|
|
1216
|
+
aggregates?: AggregateDefinition[];
|
|
1217
|
+
maxRows?: number;
|
|
1218
|
+
maxDepth?: number;
|
|
1219
|
+
/** Bounded leaf-to-ancestor aggregate work; default one million. */
|
|
1220
|
+
maxAggregationVisits?: number;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
975
1223
|
export declare function validateJobRecord(record: ReportJobRecord, scope: string, id: string, expectedRevision: number | null): void;
|
|
976
1224
|
|
|
977
1225
|
export declare function validateJobTransition(previous: ReportJobRecord | undefined, next: ReportJobRecord): void;
|